网络:套接字通信

前面学习了经典的进程间通信IPC机制,使得同一台计算机上运行的进程可以相互通信,接下来将描述套接字网络IPC接口,进程能够使用该接口和其它进程通信。
IP地址:有两个版本 IPv4和IPv6,没有特殊说明默认为IPv4
端口号
数据链路和IP中的地址,分别指的是MAC地址和IP地址,传输层中也有类似的概念,那就是端口号,端口号用来识别同一台计算机中进行通信的不同应用程序。
特点:
(1)是一个2字节16位的整数
(2)端口号用来标识一个进程,告诉操作系统当前的这个数据要交给哪一个进程来处理
(3)IP地址+端口号能够标识网络上某一台主机的某一个进程
(4)一个端口号只能被一个进程占用
套接字:
在TCP/IP协议中,”IP+地址+TCP/UDP端口号”唯一标示网络通讯中的一个进程,IP地址+端口号”就称为socket。
网络字节序:

网络中要实现通信少不了数据的传输,所以这里就引入了网络字节序的概念。
前面学习了内存中的多字节数据相对于内存地址有大端小端之分(大端:数据的低位在高地址,高位在低地址。小端:数据的低位在低地址,高位在高地址)。网络数据流同样有大端小端之分,TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主机字节序转为网络字节序,long类型
uint16_t htons(uint16_t hostshort); //主机字节序为网络字节序,short类型
uint32_t ntohl(uint32_t netlong); //网络字节序转为主机字节序,long类型
uint16_t ntohs(uint16_t netshort); //网络字节序转为主机字节序,short类型

如果主机是小端字节这些函数将参数做相应的大小端转换然后返回。如果主机是大端字节,这些函数不做转换,将参数原封不动地返回。
相关函数分析:
(1)创建套接字函数socket
这里写图片描述
参数:domain表示创建socket的类型,可选类型如下
一般IPv4参数指定AF_NET
这里写图片描述
type确定套接字的类型,可选类型如下:
对于UDP协议,type参数指定为SOCK_DGRAM,表示面向数据报的协议
对于TCP协议,type参数指定为SOCK_STAREAM,表示面向流的传输协议
这里写图片描述
参数protocol表示创建的方式,通常为0表示按给定的域和套接字类型选择默认协议。
返回值:
成功返回套接字描述符,出错返回-1
(2)将套接字和地址绑定
这里写图片描述
作用:将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
参数:sockfd服务器的套接字,也就是socket函数的正确返回值。
addr是socket服务器的地址内容。
虽然函数原型中结构体是sockaddr,但是IPv4选择的结构体是sockaddr_in,包括16位端口号和32位的IP地址。这里类似于mem族函数的参数是void*类型,以便接收各种类型的数据。
bind()成功返回0,失败返回-1
客户端一般不需要调用bind(),服务器也不是必须必须调用bind(),如果不调用内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
这里写图片描述
(3)数据发送函数
这里写图片描述
参数:
sockfd:表示当前的socket的fd。
buf:待发送数据的缓冲区。
size:缓冲区的长度。
falgs:调用方式标志位,一般位0,改变flags,将会改变sendto发送的方式
addr:指向目的套接字的地址。
addrlen:所指地址的长度。
(4)数据接收函数
这里写图片描述
参数与上面函数类似
(5)listen
这里写图片描述
listen声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态。
简单的UDP网络程序
(6)accept
这里写图片描述
三次握手完成后,服务器调用accept()接收连接。
addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addr为空表示不关心客户端的地址。
(6)connect
这里写图片描述
客户端需要调用connect()连接服务器
参数与bind参数形式一致,区别在于bind是自己的地址,而connect是对方的地址。
(7)in_addr转字符串的函数:

char *inet_ntoa(struct in_addr in);
const char *inet_ntop(int af, const void *src,
char *dst, socklen_t size);
其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr还可以转换IPv6的in_addr
多线程环境下建议使用后者,这个函数由调用者提供一个缓冲区保存结果可以规避线程安全问题
inet_ntoa(不可重入函数)返回结果在静态存储区,不需要我们手动释放,多次调用结果会覆盖
正如下图所示,第二次的地址会覆盖第一次的地址。(conteos7上测试可能没有问题)
这里写图片描述
结果:
这里写图片描述
简单的UDP网络程序
服务器:
这里写图片描述
客户端:
这里写图片描述
简单的TCP网络程序
服务器:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/types.h>
  4 #include<sys/socket.h>
  5 #include<netinet/in.h>
  6 #include<arpa/inet.h>
  7 #include<string.h>
  8 #define MAX 128
  9 int startup(char*ip,int port)
 10 {
 11     int sock = socket(AF_INET,SOCK_STREAM,0);
 12     if(sock<0)
 13     {
 14         printf("socket error!\n");
 15         exit(2);
 16     }//填充结构体进行绑定
 17     struct sockaddr_in local;
 18     local.sin_family=AF_INET;
 19     local.sin_addr.s_addr=inet_addr(ip);
 20     local.sin_port=htons(port);
 21     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
 22         printf("bind error!\n");
 23         exit(3);
 24     }//设置套接字为监听状态
 25     if(listen(sock,5)<0){
 26         printf("listen error!\n");
 27         exit(4);
 28     }
 29     return sock;
 30 }
 31 void service(int sock,char *ip,int port)
 32 {
 33     char buf[MAX];
 34     while(1){
 35         buf[0]=0;//每次进来清空缓冲区
 36         ssize_t s =read(sock,buf,sizeof(buf)-1);
 37         if(s>0){
 38             buf[s]=0;
 39             printf("[%s:%d] say# %s\n",ip,port,buf);
 40         write(sock,buf,strlen(buf));}
 41 
 42     else if(s==0)
 43     {
 44         printf("client [%s:%d] quit !\n",ip,port);
 45         break;
 46     }else{
 47         printf("read error!\n");
 48         break;
 49     }
 50 
 51 }
 52 }
 53 int main(int argc,char *argv[])
 54 {
 55     if(argc != 3)
 56     {
 57         printf("Usage: %s [ip] [port]\n",argv[0]);
 58         return 1;
 59     }
 60     int listen_sock = startup(argv[1],atoi(argv[2]));
 61     struct sockaddr_in peer;
 62     char ipBuf[24];//定义缓冲区
 63     for(;;){
 64         ipBuf[0]=0;
 65         socklen_t len  = sizeof(peer);
 66         int new_sock = accept(listen_sock,(struct        sockaddr*)&peer,&len);
 67          if(new_sock<0){
 68             printf("accept error!\n");
 69             continue;
 70 
 71         }
 72         inet_ntop(AF_INET,(const void*)&peer.sin_addr,ipBuf,sizeof(ipBuf));
 73         //获得新链接
 74         int p = ntohs(peer.sin_port);
 75         printf("get a new connect,[%s:%d]\n",ipBuf,p);
 76         service(new_sock,ipBuf,p);
 77         close(new_sock);
 78     }
 79 
 80     return 0;
 81 }

客户端:

 1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/types.h>
  4 #include<sys/socket.h>
  5 #include<netinet/in.h>
  6 #include<arpa/inet.h>
  7 #include<string.h>
  8 #define MAX 128
  9 
 10 int main(int argc,char *argv[])
 11 {
 12     if(argc != 3){
 13         printf("Usage:%s [ip] [port]\n",argv[0]);
 14         return 1;
 15     }
 16     int sock = socket(AF_INET,SOCK_STREAM,0);
 17     if(sock<0){
 18         printf("socket error!\n");
 19         return 2;
 20     }
 21     struct sockaddr_in server;
 22     server.sin_family = AF_INET;
 23     server.sin_port = htons(atoi(argv[2]));
 24     server.sin_addr.s_addr=inet_addr(argv[1]);
 25    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){
 26        printf("connect error!\n");
 27        return 3;
 28    }
 29    char buf[MAX];
 30    while(1){
 31        printf("please Enter# ");
 32        fflush(stdout);
 33        ssize_t s = read(0,buf,sizeof(buf)-1);
 34        if(s>0){
 35            buf[s-1]=0;
 36            if(strcmp("quit",buf)==0){
 37                printf("client quit!\n");
 38                break;
 39            }
 40            write(sock,buf,strlen(buf));
 41            s=read(sock,buf,sizeof(buf)-1);
 42            buf[0]=0;
 43            printf("Server Echo# %s\n",buf);
 44    }
 45    }
 46      close(sock);
 47     return 0;
 48 }

编译运行:
这里写图片描述
查看监听状态:
这里写图片描述
比较总结多进程版本和多线程版本:
多进程版本:通过每个请求,创建子进程的方式来支持多连接。

优点:
(1)链接来才创建子进程,性能受损,时间慢。
(2)只能服务有限个客户。
(3)调度器有压力,cpu调度压力大影响性能,影响客户端等待时间。
缺点:
(1)可处理多个客户
(2)多进程服务器简单,编写周期短
(3)稳定性强

多线程版本:通过每个请求,创建一个线程的方式支持多连接。
多线程所有的优点都以牺牲稳定性为代价,有可能因线程安全问题导致服务器挂掉。

猜你喜欢

转载自blog.csdn.net/zjx624bjh/article/details/80294551