网络编程---UDP TCP
认识socket
netstat -nltp 查看网络服务
socket可以看成是用户进程与内核网络协议栈的编程接口。
socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
认识UDP协议(传输控制协议)
1>传输层协议
2>无连接
3>不可靠传输
4>面向数据报(发送多少必须接收多少)
认识TCP协议(传输控制协议)
1>传输层协议
2>有连接(唤起对方的意识)
3>可靠传输(可以得到反馈信息)
4>面向字节流(发送与接收随意)
网络字节序我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,使用一下库函数完成网络字节序到主机字节序的转换
#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编程接口
socket 常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len); // 开始监听socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len); // 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockaddr 结构
在linux环境下,结构体struct sockaddr在/usr/include/linux/socket.h中定义,具体如下: typedef unsigned short sa_family_t; struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }
sockaddr_in 结构
在linux环境下,结构体struct sockaddr_in在/usr/include/netinet/in.h中定义,具体如下: /* Structure describing an Internet socket address. */ struct sockaddr_in { __SOCKADDR_COMMON (sin_); in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; /* 字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 */ };
in_addr 结构
struct in_addr其实就是32位IP地址 struct in_addr { unsigned long s_addr; };
UDP 接口
1#include <sys/types.h> 2#include <sys/socket.h> int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen); int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen); sendto(),是把UDP数据报发给指定地址;recvfrom()是从指定地址接收UDP数据报。 对于sendto()函数,成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。 对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。
地址转换函数
字符串转in_addr的函数:
#include <arpa/inet.h> int inet_aton (const char *__cp, struct in_addr *__inp); in_addr_t inet_addr (const char *__cp); int inet_pton (int __af, const char *__restrict __cp, void *__restrict __buf);
in_addr转字符串的函数:
char *inet_ntoa(struct in_addr inaddr); const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);inet_ntoa 将一个十进制网络字节序转换为点分十进制IP格式的字符串。
inet_addr 将一个点分十进制的IP转换成一个长整数型数
简单的UDP网络程序
UDP_server.c1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/socket.h> 4 #include<netinet/in.h> 5 #include<arpa/inet.h> 6 #include<string.h> 7 //#include<unistd.h> 8 #include<errno.h> 9 10 void usage(char *proc){ 11 printf("usage:%s[server_ip][server_proc]\n",*proc); 12 } 13 int main(int argc,char*argv[]){ 14 if(argc!=3){ 15 usage(argv[0]); 16 return 1; 17 } 18 int sock = socket(AF_INET,SOCK_DGRAM,0);//创建socket文件描述符,ip类型为ipv4,传输层协议为UDP 19 //服务器的相关信息 20 struct sockaddr_in local; 21 local.sin_family=AF_INET;//ip类型 22 local.sin_port=htons(atoi(argv[2]));//端口号----把客户端输入的端口号转换成协议可以识别的 23 local.sin_addr.s_addr = inet_addr(argv[1]);//ip地址----把客户端输入的点分十进制转换成协议可以识别的 24 //绑定服务器自己,为了让客户端找到自己 25 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) 26 { perror("bind"); 27 return 2; 28 } 29 //处理链接上的客户端 30 char buf[1024]; 31 struct sockaddr_in client;//用来保存客户端的信息,因为处理好还要发送给客户端 32 while(1){//不停的获取来自客户端的信息 33 socklen_t len = sizeof(client); 34 //接收来自client端的数据存到buf指定的内存空间里,成功返回接收到的字符数,失败返回-1; 35 ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len); 36 if(s>0){ 37 buf[s]=0; 38 //客户端ip:客户端端口号:客户端输入的内容 39 printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf); 40 //服务器把数据处理好后发送给客户端指定的地址 41 sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client)); 42 } 43 } 44 close(sock); 45 return 0; 46 }
UDP_client.c
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/socket.h> 4 #include<netinet/in.h> 5 #include<arpa/inet.h> 6 #include<string.h> 7 #include<errno.h> 8 //对于客户端而言需要输入ip,端口号进行访问服务器//./client ip porc 9 void usage(char *proc){ 10 printf("usage:%s[server_ip] [server_port]\n",proc); 11 } 12 int main(int argc,char *argv[]){ 13 if(argc!=3){ 14 usage(argv[0]); 15 return 1; 16 } 17 int sock = socket(AF_INET,SOCK_DGRAM,0); 18 if(socket<0){ 19 perror("socket"); 20 return 2; 21 } 22 //客户端一般不建议绑定端口号,系统会自动给加上端口号,链接断开后自动解除 23 //客户端也不需要监听,监听一般是被连的一方 24 //所以对于客户端而言需要进行链接操作 25 //所以需要定义一个结构体来储存服务器的信息方便客户端链接 26 struct sockaddr_in server; 27 server.sin_family = AF_INET; 28 server.sin_port = htons(atoi(argv[2])); 29 server.sin_addr.s_addr = inet_addr(argv[1]); 30 char buf[1024]; 31 struct sockaddr_in ret; 32 while(1){ 33 socklen_t len = sizeof(ret); 34 printf("please enter# "); 35 fflush(stdout); 36 //把客户端输入的信息保存到buf缓冲区中 37 ssize_t s = read(0,buf,sizeof(buf)-1); 38 if(s>0){ 39 buf[s-1]=0; 40 //把buf的数据发送给服务器 41 sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server)); 42 //接收来自服务器处理好的数据//接收远程主机经指定的socket传来的数据并用ret保存源地址 43 ssize_t _s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&ret,&len); 44 if(_s>0){ 45 buf[s]=0; 46 printf("server echo# %s\n",buf); 47 } 48 } 49 } 50 close(sock); 51 return 0; 52 }结果:
简单的TCP网络程序
TCP_client.c
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/socket.h> 4 #include<netinet/in.h> 5 #include<arpa/inet.h> 6 #include<string.h> 7 //客户端需输入./client ip port 8 void usage(const char *proc){ 9 printf("usage: %s [server_ip][server_proc]\n",proc); 10 } 11 int main(int argc,char *argv[]){ 12 if(argc!=3){ 13 usage(argv[0]); 14 return 1; 15 } 16 int sock = socket(AF_INET,SOCK_STREAM,0); 17 if(sock<0){ 18 perror("socket"); 19 return 2; 20 } 21 //对于客户端而言一般不建议绑定,操作系统会随机生成客户端的端口号,链接退出后,系统会自动消除 22 //对于客户端而言也不需要监听,因为并没有人来链接,所有也不许accept 23 //客户端最重要的是链接操作 24 //connect();需要知道链接的端口号和ip 25 struct sockaddr_in server; 26 server.sin_family = AF_INET; 27 server.sin_port=htons(atoi(argv[2])); 28 server.sin_addr.s_addr = inet_addr(argv[1]); 29 socklen_t len = sizeof(server); 30 if(connect(sock,(struct sockaddr*)(&server),len)<0){ 31 perror("connect"); 32 return 3; 33 } 34 //服务器先读后写 35 //客户端先写后读 36 char buf[1024]; 37 while(1){ 38 printf("please enter# ") ; 39 fflush(stdout); 40 ssize_t s = read(0,buf,sizeof(buf)-1); 41 if(s>0){ 42 buf[s-1]=0; 43 write(sock,buf,strlen(buf)); 44 ssize_t _s = read(sock,buf,sizeof(buf)-1); 45 if(s>0){ 46 buf[_s]=0; 47 printf("server echo# %s\n",buf); 48 } 49 } 50 } 51 close(sock); 52 return 0; 53 }
TCP_server.c(单进程)
1 #include<sys/socket.h> 2 #include<netinet/in.h> 3 #include<sys/types.h> 4 #include<arpa/inet.h> 5 #include<string.h> 6 #include<stdio.h> 7 void usage(const char * ret){ 8 printf("usage:%s[server_ip][server_proc]\n",ret); 9 } 10 //argv[0]代表可执行文件名,argv[1]代表ip地址,argv[2]代表端口号 11 int main(int argc,char *argv[]){ 12 if(argc!=3){ 13 usage(argv[0]); 14 return 1; 15 } 16 //sock参数1,指定协议版本一般为iPv4 17 //参数2,指定传输层协议(UDP为面向数据报,TCP为面向字节流) 18 //参数3,一般为0; 19 //创建监听套接字 20 int listen_sock = socket(AF_INET,SOCK_STREAM,0); 21 if(listen_sock<0){ 22 perror("socket"); 23 return 2; 24 } 25 struct sockaddr_in local; 26 local.sin_family = AF_INET; 27 local.sin_port = htons(atoi(argv[2]));//将主机的无符号短整形数转换成网络字节顺序。 28 local.sin_addr.s_addr = inet_addr(argv[1]); 29 //对于服务器而言,需要绑定自己,因为要保证客户端能够时刻找到自已 30 if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){ 31 perror("bind"); 32 return 3; 33 } 34 //对于服务器而言,同时也需要监听,保证客户端任何时候访问时,能够及时服务 35 if(listen(listen_sock,10)<0){ 36 perror("listen"); 37 } 38 //对于服务器而言listen_sock只负责拉客人,而new_sock负责服务客人 39 //accept()接收新链接 40 while(1){ 41 struct sockaddr_in client; 42 socklen_t len = sizeof(client); 43 int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len); 44 if(new_sock<0){ 45 perror("accept"); 46 continue; 47 } 48 //获取到新链接后处理客户端数据 49 char buf[1024]; 50 while(1){ 51 //从获取的新链接里读数据放到缓冲区里 52 ssize_t s = read(new_sock,buf,sizeof(buf)-1); 53 //对于服务器而言读到/0就代表链接关闭了 54 if(s>0){ 55 buf[s]=0; 56 printf("client# %s\n",buf); 57 write(new_sock,buf,strlen(buf));//把buf的内容写到客户端那里去 58 } 59 else if(s==0){ 60 printf("client quit\n"); 61 break; 62 } 63 else perror("read"); 64 } 65 close(new_sock); 66 } 67 return 0; 68 }
TCP_more_server.c(多进程)
1 #include<sys/socket.h> 2 #include<netinet/in.h> 3 #include<sys/types.h> 4 #include<arpa/inet.h> 5 #include<string.h> 6 #include<stdio.h> 7 #include<signal.h> 8 void process_connect(int new_sock){ 9 pid_t pid = fork(); 10 if(pid<0){ 11 perror("fork"); 12 return; 13 } 14 else if(pid>0){ 15 //父进程 16 //此处若是父进程直接退出,会造成僵尸进程 17 //如果调用wait();阻塞式等待,会造成没有及时接收客户端新的链接 18 //如果调用waitpid();非阻塞等待,如果只调用一次,就会出现叫子进程没退出,父进程就退出了,会出现僵尸进程, 19 //如果采用轮询的方式调用,虽然处理了僵尸进程,但是没有及时accept 20 //更合理的方式是捕捉信号SIGCHLD(当我们忽略SIGCHLD信号,内核将把僵尸进程交由init进程去处理,能够省去大量僵尸进程占用系统资源。) //SIGCHLD,在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程,按系统默认将忽略此信号, //如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。 21 return; 22 } 23 else{ 24 //获取到新链接后处理客户端数据 25 char buf[1024]; 26 while(1){ 27 //从获取的新链接里读数据放到缓冲区里 28 ssize_t s = read(new_sock,buf,sizeof(buf)-1); 29 //对于服务器而言读到/0就代表链接关闭了 30 if(s>0){ 31 buf[s]=0; 32 printf("client# %s\n",buf); 33 write(new_sock,buf,strlen(buf));//把buf的内容写到客户端那里去 34 } 35 else if(s==0){ 36 printf("client quit\n"); 37 break; 38 } 39 else perror("read"); 40 } 41 close(new_sock); 42 } 43 } 44 void usage(const char * ret){ 45 printf("usage:%s[server_ip][server_proc]\n",ret); 46 } 47 //argv[0]代表可执行文件名,argv[1]代表ip地址,argv[2]代表端口号 48 int main(int argc,char *argv[]){ 49 if(argc!=3){ 50 usage(argv[0]); 51 return 1;52 } 53 signal(SIGCHLD,SIG_IGN); 54 //sock参数1,指定协议版本一般为iPv4 55 //参数2,指定传输层协议(UDP为面向数据报,TCP为面向字节流) 56 //参数3,一般为0; 57 //创建监听套接字 58 int listen_sock = socket(AF_INET,SOCK_STREAM,0); 59 if(listen_sock<0){ 60 perror("socket"); 61 return 2; 62 } 63 struct sockaddr_in local; 64 local.sin_family = AF_INET; 65 local.sin_port = htons(atoi(argv[2]));//将主机的无符号短整形数转换成网络字节顺序。 66 local.sin_addr.s_addr = inet_addr(argv[1]); 67 //对于服务器而言,需要绑定自己,因为要保证客户端能够时刻找到自已 68 if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){ 69 perror("bind"); 70 return 3; 71 } 72 //对于服务器而言,同时也需要监听,保证客户端任何时候访问时,能够及时服务 73 if(listen(listen_sock,10)<0){ 74 perror("listen"); 75 } 76 //对于服务器而言listen_sock只负责拉客人,而new_sock负责服务客人 77 //accept()接收新链接 78 while(1){ 79 struct sockaddr_in client; 80 socklen_t len = sizeof(client); 81 int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len); 82 if(new_sock<0){ 83 perror("accept"); 84 continue; 85 } 86 process_connect(new_sock); 87 } 88 return 0; 89 } 72 //对于服务器而言,同时也需要监听,保证客户端任何时候访问时,能够及时服务 73 if(listen(listen_sock,10)<0){ 74 perror("listen"); 75 } 76 //对于服务器而言listen_sock只负责拉客人,而new_sock负责服务客人 77 //accept()接收新链接 78 while(1){ 79 struct sockaddr_in client; 80 socklen_t len = sizeof(client); 81 int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len); 82 if(new_sock<0){ 83 perror("accept"); 84 continue; 85 } 86 process_connect(new_sock); 87 } 88 return 0; 89 }
TCP_more1_server.c(多线程)
1 #include<sys/socket.h> 2 #include<netinet/in.h> 3 #include<sys/types.h> 4 #include<arpa/inet.h> 5 #include<string.h> 6 #include<stdio.h> 7 #include<pthread.h> 8 #include<malloc.h> 9 //我们在线程入口函数里需要用到新链接的描述符,也需要知道对方客户端的信息,所以我们需要一个结构体来传我们的两个参数 10 typedef struct Arg{ 11 int _new_sock; 12 struct sockaddr_in _client; 13 }Arg,*pArg; 14 void * ThreadEntry(void *ret){ 15 pArg temp = (pArg)ret; 16 //获取到新链接后处理客户端数据 17 char buf[1024]={0}; 18 while(1){ 19 //从获取的新链接里读数据放到缓冲区里 20 ssize_t s = read(temp->_new_sock,buf,sizeof(buf)-1); 21 //对于服务器而言读到/0就代表链接关闭了 22 if(s>0){ 23 buf[s]=0; 24 printf("%d client# %s\n",temp->_new_sock,buf); 25 write(temp->_new_sock,buf,strlen(buf));//把buf的内容写到客户端那里去 26 } 27 else if(s==0){ 28 printf("client quit\n"); 29 break; 30 } 31 else perror("read"); 32 } 33 close(temp->_new_sock); 34 return NULL; 35 } 36 void ProcessConnect(int new_sock,struct sockaddr_in client){ 37 //创建一个新的线程,让新的线程来回循环进行读写 38 //主线程继续回头去调用accept 39 pthread_t tid; 40 pArg arg =(pArg)malloc(sizeof(Arg)); 41 //这里注意如果这里直接在栈上直接 Arg arg而不在堆上申请是不可以的,因为ProcessConnect函数 42 //一执行就退出了,arg也就释放了,而线程入口函数会执行很久,也可能一会会,如果很久(即ProcessConnect退出了,而线程 43 //入口函数还在执行,就会导致非法访问) 44 //若为全局变量也不行,只有一份arg,好多个客户端访问就会覆盖 45 arg->_new_sock = new_sock; 46 arg->_client = client; 47 //第一个参数是一个输出型参数,表示调用该函数返回的新线程id是多少 48 //第三个参数是表示线程入口函数是谁 49 //第四个参数是表示线程入口函数的参数 50 pthread_create(&tid,NULL,ThreadEntry,(void *)arg); 51 //创建的线程也是需要等待和回收的 52 pthread_detach(tid);//分离新线程和主线程,就不要主线程回收了 53 } 54 void usage(const char * ret){ 55 printf("usage:%s[server_ip][server_proc]\n",ret); 56 } 57 //argv[0]代表可执行文件名,argv[1]代表ip地址,argv[2]代表端口号 58 int main(int argc,char *argv[]){ 59 if(argc!=3){ 60 usage(argv[0]); 61 return 1; 62 } 63 //sock参数1,指定协议版本一般为iPv4 64 //参数2,指定传输层协议(UDP为面向数据报,TCP为面向字节流) 65 //参数3,一般为0; 66 //创建监听套接字 67 int listen_sock = socket(AF_INET,SOCK_STREAM,0); 68 if(listen_sock<0){ 69 perror("socket"); 70 return 2; 71 } 72 struct sockaddr_in local; 73 local.sin_family = AF_INET; 74 local.sin_port = htons(atoi(argv[2]));//将主机的无符号短整形数转换成网络字节顺序。 75 local.sin_addr.s_addr = inet_addr(argv[1]); 76 //对于服务器而言,需要绑定自己,因为要保证客户端能够时刻找到自已 77 if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){ 78 perror("bind"); 79 return 3; 80 } 81 //对于服务器而言,同时也需要监听,保证客户端任何时候访问时,能够及时服务 82 if(listen(listen_sock,10)<0){ 83 perror("listen"); 84 } 85 //对于服务器而言listen_sock只负责拉客人,而new_sock负责服务客人 86 //accept()接收新链接 87 while(1){ 88 struct sockaddr_in client; 89 socklen_t len = sizeof(client); 90 int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len); 91 if(new_sock<0){ 92 perror("accept"); 93 continue; 94 } 95 ProcessConnect(new_sock,client); 96 } 97 return 0; 98 }