Linux下TCP、UDP网络编程框架
TCP网络编程框架
3、字节序转换函数。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); /*主机字节序到网络字节序的长整型转换*/
uint16_t htons(uint16_t hostshort); /*主机字节序到网络字节序的短整型转换*/
uint32_t ntohl(uint32_t netlong); /*网络字节序到主机字节序的长整型转换*/
uint16_t ntohs(uint16_t netshort); /*网络字节序到主机字节序的短整型转换*/
二、Socket套接字
1、Liunx中的网络编程通过Socket(套接字)实现,Socket是一种文件描述符。
2、socket有三种类型:
流式套接字(SOCK_STREAM):TCP协议。
数据报套接字(SOCK_DGRAM):UDP协议。
原始套接字(SOCK_RAW):允许使用IP协议,用于新的网络协议的测试等。
3、套接字的地址结构:
a、 以太网:互联网就是把多个同种类或者不同种类的小网络用路由器连起来。以太网是最为常用的小网络,或者说局域网。
进行套接字编程需要指定套接字的地址作为参数,不同的协议族有不同的地址结构定义方式。
这些地址结构通常以sockaddr_开头,每一个协议族有一个唯一的后缀,以太网协议族的地址结构为sockaddr_in。
b、Socket编程常用的地址结构
在socket程序设计中,struct sockaddr_in用于保存socket地址信息:
#include <netinet/in.h>
struct sockaddr_in
{
short int sin_family; /* Internet地址族 */
unsigned short int sin_port; /* 端口号,网络字节序 */
struct in_addr sin_addr; /* IP地址,网络字节序 */
unsigned char sin_zero[8]; /* 填0 以保持与 struct sockaddr 同样大小*/
};
其中:
struct in_addr //网络IP的类型
{
unsigned long s_addr;
};
三、字符串IP地址和二进制IP地址的转换
1、inet_aton()函数和inet_ntoa()函数
#include<netddb.h>
(1) int inet_aton(const char *cp,struct in_addr *inp) //点分十进制转化为二进制IP,结 果在struct in_addr *中,转化失败返回0
(2)char *inet_ntoa(struct in_addr in) //二进制转化为点分十进制,结果为char *返回 值
2.inet_pton()函数和inet_ntop()函数
#include <arpa/inet.h>
(1)int inet_pton(int af, const char *src, void *dst); //[将"点分十进制" -> "整数"]
这个函数转换字符串到网络地址,第一个参数af是地址族(ipv4为AF_INET),转换后存在dst指向的struct in_addr结构体中。
(2)const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); //将"整数" -> “点分十进制”
这个函数转换网络二进制结构到点分十进制的地址,参数的作用和上面相同,只是多了一个参数socklen_t cnt,他是所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。
四、IP和主机名之间的转换
在网络上标识一台机器可以用IP,也可以使用主机名。
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname)
函数说明:实现主机名(网址(需联网)等)到IP的转换
函数返回值:
struct hostent
{
char *h_name; /* 主机的正式名称*/
char **h_aliases; /* 主机的别名列表,以NULL结尾*/
int h_addrtype; /* 主机的地址类型 AF_INET(ipv4)*/
int h_length; /* 主机的ip地址长度,ipv4为4*/
char **h_addr_list;/*主机的IP号的地址列表(为32位网络地址,而不是字符串),以NULL结尾*/
}
五、TCP网络编程架构
TCP网络编程有两种模式,一种是服务器模式,另一种是客户端模式。
服务器模式创建一个服务程序,等待客户端用户的连接,接收到用户的连接请求后,根据用户的请求进行处理;
客户端模式则根据目的服务器的地址和端口进行连接,向服务器发送请求并对服务器的响应进行数据处理。
1、套接字初始化(socket())
socket()函数介绍
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol); //如果函数调用成功,会返回一个表示这个 套接字的文件描述符,失败的时候返回–1。
参数:
a.domain即协议域,又称为协议族(family),AF_INET
b.type指定socket类型。常用的socket类型有,SOCK_STREAM、 SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
c.protocol:故名思意,就是指定协议,一般设置为0
2、套接字与端口的绑定(bind())
(1)、在服务器设计中,建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑定,才能进行数据的接收和发送操作。
(2)、bind()函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
//将长度为addrlen的struct sockaddr类型的参数my_addr与sockfd绑定在一起,将so
cket绑定到某个端口上
3、设置服务器的侦听连接(listen())
(1)、函数listen()用来初始化服务器可连接队列。
服务器处理客户端连接请求的时候是顺序处理的,同一时间仅能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度由listen()函数来定义。
(2)、listen()函数
listen()函数的原型如下,其中的backlog表示等待队列的长度。
#include <sys/socket.h>
int listen(int sockfd, int backlog); //返回值为-1时,说明监听失败
4、接受客户端连接(accept())//连接服务器(connect())
(1)、当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,直到使用服务器处理接收请求。
函数accept()成功执行后,会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得。
(2)、accept()
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd为服务器的socket描述字
addr为指向struct sockaddr *的指针,用于返回客户端的协议地址
addrlen为指向协议地址长度指针。
结果:
如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接;否则返回-1;
注意:
1.accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字,一个服务器通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。
2.而accept函数返回的是已连接的socket描述字。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
(3)、客户端在建立套接字之后,不需要进行地址绑定就可以直接连接服务器。连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址、端口等。
(4)、connect()
connect()函数的原型如下。
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr* addr, int addrlen);
5、接受和发送数据(read()、write())
(1)、当服务器端在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作。对套接字进行写入的形式和过程与普通文件的操作方式一致,内核会根据文件描述符的值来查找所对应的属性,当为套接字的时候,会调用相对应的内核函数。
(2)、write()
int size ;
char data[1024];
size = write(sockfd, data, 1024);
(3)、使用read()函数可以从套接字描述符中读取数据。在读取数据之前,必须建立套接字并连接。
(4)、read()
int size ;
char data[1024];
size = read(s, data, 1024);
6、数据处理及套接字关闭(close())
close(int sockfd);
下面是具体实现代码:
服务端代码:sy2.c
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <strings.h>
main()
{
pid_t pid;
int socket_fd,accept_fd; //socket套接字文件描述符
int addrlen,iBytes;
unsigned char buf[256];
struct sockaddr_in server,client; //用于保存socket地址信息
/*
服务端的流程:socket()--->bind()--->listen()--->accept()--->read()--->write()--->close()
*/
/****************socket()*****************/
/* 服务器端开始建立socket_fd描述符*/
//套接字初始化(socket()),如果函数调用成功,会返回一个表示这个 套接字的文件描述符,失败的时候返回–1。
if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
perror("Error--socket:");
_exit(-1);
}
/* 服务器端填充 sockaddr结构*/
bzero(&server,sizeof(server)); //给server分配空间初始化
server.sin_family = AF_INET; // Internet地址族
server.sin_port = htons(4000); //端口号,网络字节序
server.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,网络字节序; (将本机器上的long数据转化为网络上的long数据)服务器程序能运行在任何ip的主机上 //INADDR_ANY 表示主机可以是任意IP地址,即服务器程序可以绑定到所有的IP上
/****************bind()*****************/
//在服务器设计中,建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑>定,才能进行数据的接收和发送操作.
//将长度为addrlen的struct sockaddr类型的参数server与socket_fd绑定在一起,将socket绑定到某个端口上
if(bind(socket_fd,(struct sockaddr *)&server,sizeof(server)) == -1 ) {
perror("Error-bind:");
_exit(-2);
}
/****************listen()*****************/
//函数listen()用来初始化服务器可连接队列,其中的5表示等待队列的长度。
if(listen(socket_fd,5) == -1) {
perror("Error-listen:");
_exit(-3);
}
/****************accept()*****************/
addrlen = sizeof(client);
for(;;) {
//1.accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生
成的,称为监听socket描述字,一个服务>器通常仅仅只创建一个监听socket描述字,它在>该服务器的生命周期内一直存在。
// 2.而accept函数返回的是已连接的socket描述字。内核为每个由服务器进程接受的客户
连接创建了一个已连接socket描述字,当>服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
if((accept_fd = accept(socket_fd,(struct sockaddr *)&client,&addrlen)) == -1)
{
perror("Error-accept:");
_exit(-4);
}
pid = fork(); //如果监听成功就创建子进程
if(pid > 0) continue; //父进程继续监听
else if(pid == -1) {
perror("Error-fokr");
_exit(-5);
}
/****************read()*****************/
//以下都为子进程的动作
bzero(buf,256);
iBytes = read(accept_fd,buf,256); //从客户端读取接收到的信息
if(iBytes == -1) {
perror("Error-recv");
_exit(-6);
}
/****************write()*****************/
//inet_ntoa()函数:二进制转化为点分十进制,结果为char *返回值
//ntohs(uint16_t netshort); /*网络字节序到主机字节序的短整型转换*/
printf("[ %s:%d]发来连接请求:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
if(write(accept_fd,"Welcome.baby!\n",15) == -1) { //给客户端发出回应信息
perror("Error-send");
_exit(-7);
}
/****************close()*****************/
close(accept_fd);
_exit(0);
}
}
客户端代码:sy3.c
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <strings.h>
main()
{
int socket_fd; //socket套接字文件描述符
int addrlen,iBytes;
unsigned char buf[256];
struct sockaddr_in server; // 用于保存socket地址信息
/*
客户端的流程:socket()--->connect()--->write()--->read()--->close()
*/
/****************socket()*****************/
/* 客户程序开始建立 socket_fd描述符*/
if((socket_fd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
perror("Error--socket:");
_exit(-1);
}
bzero(&server,sizeof(server)); //给server分配空间初始化
server.sin_family = AF_INET; // Internet地址族
server.sin_port = htons(4000); //端口号,网络字节序
server.sin_addr.s_addr = inet_addr("127.0.0.1"); //IP地址,网络字节序
/****************connect()*****************/
/* 客户程序发起连接请求 */
//连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址、端口等
if(connect(socket_fd,(struct sockaddr *)&server,sizeof(server)) == -1) {
perror("Error--connect:");
_exit(-2);
}
/****************write()*****************/
if(write(socket_fd,"嗨,我是广州!\n",17) == -1) { //给服务端发出请求信息
perror("Error-write");
_exit(-3);
}
/****************read()*****************/
bzero(buf,256);
iBytes = read(socket_fd,buf,256); //读取服务端的响应信息
if(iBytes == -1) {
perror("Error-read");
_exit(-4);
}
printf("服务器的响应:%s\n",buf);
/****************close()*****************/
close(socket_fd);
_exit(0);
}
服务器端整个程序的工作过程:socket()—>bind()—>listen()—>accept()—>read()—>write()—>close()
1.首先服务器端创建socket,以及服务器和客户端的地址信息初始化;
2.再通过bind函数把socket和服务端地址绑定;
3.然后服务器开始使用listen函数进行监听;
4.监听成功后,使用accept函数接受监听内容;
5.创建子进程读取消息内容(read函数),父进程继续监听listen函数;
6.服务器对收到的消息进行处理,用write函数给客户端发出一个回应消息;
7.关闭socket
客户端整个工作过程:socket()—>connect()—>write()—>read()—>close()
1.首先创建socket,以及服务器地址信息初始化;
2.connect()函数与服务器进行TCP三次握手连接;
3.用write()函数给服务器发送消息;
4.接收服务器端的回应消息read();
5.关闭socket,close()。
参考文献链接: Linux网络编程:TCP网络编程构架.
UDP网络编程框架
- UDP编程框架
要使用UDP协议进行程序开发,我们必须首先得理解什么是什么是UDP?这里简单概括一下。
UDP(user datagram protocol)的中文叫用户数据报协议,属于传输层。UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对方。所以UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。正因为UDP无需建立类如三次握手的连接,而使得通信效率很高。
UDP的应用非常广泛,比如一些知名的应用层协议(SNMP、DNS)都是基于UDP的,想一想,如果SNMP使用的是TCP的话,每次查询请求都得进行三次握手,这个花费的时间估计是使用者不能忍受的,因为这会产生明显的卡顿。所以UDP就是SNMP的一个很好的选择了,要是查询过程发生丢包错包也没关系的,我们再发起一个查询就好了,因为丢包的情况不多,这样总比每次查询都卡顿一下更容易让人接受吧。
UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架也是比较简单的。以下是UDP的框架图。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数buf:发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
第三个参数len:发送缓冲区的大小,单位是字节
第四个参数flags:填0即可
第五个参数dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
第六个参数addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回发送成功的数据长度
失败: -1
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数buf:接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
第三个参数len:接收缓冲区的大小,单位是字节
第四个参数flags:填0即可
第五个参数src_addr:指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
第六个参数addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回接收成功的数据长度
失败: -1
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
第一个参数sockfd:正在监听端口的套接口文件描述符,通过socket获得
第二个参数my_addr:需要绑定的IP和端口
第三个参数addrlen:my_addr的结构体的大小
返回值:成功:0
失败:-1
#include <unistd.h>
int close(int fd);
close函数比较简单,只要填入socket产生的fd即可。
下面是具体实现代码:
服务端代码:sy4.c
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int socket_fd; //socket套接字文件描述符
int recv_num;
int send_num;
int client_len;
char recv_buf[20];
unsigned char buf[256];
struct sockaddr_in addr_serv,addr_client; //用于保存服务器和客户端地址信息
/*
服务端的流程:socket()--->bind()--->recvfrom()--->sendto()--->close()
*/
/****************socket()*****************/
/* 开始建立 socket_fd描述符*/
if((socket_fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
perror("Error--socket:");
exit(1);
}
//初始化服务器端地址
bzero(&addr_serv,sizeof(addr_serv)); //给server分配空间初始化
addr_serv.sin_family = AF_INET; // Internet地址族
addr_serv.sin_port = htons(4001); //端口号,网络字节序
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,网络字节序
client_len = sizeof(struct sockaddr_in);
//绑定套接字与地址
/****************bind()*****************/
if(bind(socket_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr_in)) < 0 ) {
perror("Error-bind:");
exit(1);
}
while(1)
{
/****************recvfrom()*****************/
//从客户端接收数据
recv_num = recvfrom(socket_fd,recv_buf,50,0,(struct sockaddr *)&addr_client,&client_len);
if(recv_num < 0)
{
perror("again recvfrom");
exit(1);
}else {
printf("%d\n",recv_num);
recv_buf[recv_num] = '\0';
printf("recv sucess:%s\n",recv_buf);
}
/****************sendto()*****************/
//给客户端发送数据
//printf("server:Hello,World!\n");
send_num = sendto(socket_fd,"Hello!我是服务器!收到啦",50,0,(struct sockaddr *)&addr_client,client_len);
if(send_num < 0)
{
perror("sendto");
exit(1);
}else {
//printf("send sucess:%s\n",recv_buf);
}
}
/****************close()*****************/
close(socket_fd);
return 0;
}
客户端代码:sy5.c
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <strings.h>
int main()
{
int recv_num;
char recv_buf[512];
/*
客户端的流程:socket()--->sendto()--->recvfrom()--->close()
*/
struct sockaddr_in addr_serv;
bzero(&addr_serv,sizeof(addr_serv)); //给server分配空间初始化
addr_serv.sin_family = AF_INET; // Internet地址族
addr_serv.sin_port = htons(4001); //端口号,网络字节序
addr_serv.sin_addr.s_addr = inet_addr("192.168.43.130"); //IP地址,网络字节序
/****************socket()*****************/
int socket_fd;
if((socket_fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
perror("Error--socket:");
exit(1);
}
/****************sendto()*****************/
//给服务器发送信息,其中(struct sockaddr *)&addr_serv是目的地址,sizeof(addr_serv)是目的地址大小
if(sendto(socket_fd,"Hello,我是客户端!\n",50,0,(struct sockaddr *)&addr_serv,sizeof(addr_serv)) < 0) { //给服务端发出请求信息
perror("Error-sendto");
exit(1);
}
/****************recvfrom()()*****************/
//接收来自服务器端的数据,其中,(struct sockaddr *)&addr_serv是发送方的地址,即服务器地址
int server_len = sizeof(struct sockaddr_in);
//recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&addr_serv, &server_len);
//recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,NULL, NULL);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//这里出错了,找了老半天。因为括号没打,所以导致recv_num=0,应该要把整个运算括起来,再判断是否 < 0才正确
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if( (recv_num = recvfrom(socket_fd,recv_buf,sizeof(recv_buf),0,NULL, NULL) ) < 0)
{
perror("again recvfrom");
exit(1);
}
printf("%d\n",recv_num);
printf("recv sucess:%s\n",recv_buf);
/****************close()*****************/
close(socket_fd);
return 0;
}
服务器端整个程序的工作过程:socket()—>bind()—>recvfrom()—>sendto()—>close()
1.首先服务器端创建socket,以及服务器和客户端的地址信息初始化;
2.再通过bind函数把socket和服务端地址绑定;
3.服务器接收客户端发送的消息==>recvfrom()函数
3.服务器对收到的消息进行处理,用sendto()函数给客户端发出一个回应消息;
7.关闭socket,close()。
客户端整个工作过程:socket()—>sendto()—>recvfrom()—>close()
1.首先创建socket,以及服务器地址信息初始化;
2.用sendto()函数给服务器发出一个请求消息;
3.用recvfrom()函数给接收服务器端的响应消息;
4.关闭socket,close()。
参考文献链接: Linux编程之UDP SOCKET全攻略.