用Socket编程之TCP/IP通信,你会了吗?
OSI参考模型
OSI(Open System Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。
ISO为了更好的使网络应用更为普及,推出了OSI参考模型。其含义就是推荐所有公司使用这个规范来控制网络。这样所有公司都有相同的规范,就能互联了。
OSI定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层),即ISO开放互连系统参考模型。如下图。
每一层实现各自的功能和协议,并完成与相邻层的接口通信。OSI的服务定义详细说明了各层所提供的服务。某一层的服务就是该层及其下各层的一种能力,它通过接口提供给更高一层。各层所提供的服务与这些服务是怎么实现的无关。
TCP/IP协议
叫做传输控制/网际协议,又叫网络通信协议。实际上,它包含上百个功能的协议,如ICMP(互联网控制信息协议)、FTP(文件传输协议)、UDP(用户数据包协议)、ARP(地址解析协议)等。TCP负责发现传输的问题,一旦有问题就会发出重传信号,直到所有数据安全正确的传输到目的地。
理解socket
1、socket即为套接字,在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一的标识网络通讯中的一个进程,“IP地址+TCP或UDP端口号”就为socket。
2、在TCP协议中,建立连接的两个进程(客户端和服务器)各自有一个socket来标识,则这两个socket组成的socket pair就唯一标识一个连接。
3、socket本身就有“插座”的意思,因此用来形容网络连接的一对一关系,为TCP/IP协议设计的应用层编程接口称为socket API。
套接字(socket):
在网络中用来描述计算机中不同程序与其他计算机程序的通信方式。socket其实是一种特殊的IO接口,也是一种文件描述符。
套接字分为三类:
流式socket(SOCK_STREAM):流式套接字提供可靠、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
数据报socket(SOCK_DGRAM):数据报套接字定义了一种无连接的服务,数据通过相互独立的保温进行传输,是无序的,并且不保证是可靠、无差错的。它使用的数据报协议是UDP。
原始socket:原始套接字允许对底层协议如IP或ICMP进行直接访问,它功能强大但使用复杂,主要用于一些协议的开发。
套接字由三个参数构成:IP地址,端口号,传输层协议。这三个参数用以区分不同应用程序进程间的网络通信与连接。
套接字的数据结构:C语言进行套接字编程时,常会使用到sockaddr数据类型和sockaddr_in数据类型,用于保存套接字信息。
TCP服务器的工作过程如下:
通信过程
服务器:
创建套接字,返回套接字的文件描述符skfd = socket()
将套接字文件描述符、ip、端口号绑定在一起,建立固定的对应关系bind()
将套接字文件描述符转为被动描述符,用于被动监听客户端链接listen()
与客户端三次握手成功,返回一个通信描述符 fd=accept()
服务器向客户端发送、接收数据 write(fd);send(fd),read(fd);recv(fd);
四次挥手断开连接,可以由任意方发起close(fd);shutdown(fd)
#include <sys/types.h>#include <sys/socket.h> //包含套接字的函数库#include <netinet/in.h> //包含AF_INET的相关结构#include <arpa/inet.h> //包含AF_INET的操作函数#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h> /* 监听后,一直处于accept阻塞状态, 直到有客户端连接, 输入EOF后,断开与客户端的连接,输入quit关闭服务器读取EOF后,断开和此客户端连接,读取quit关闭服务器 */ #define PORT 3333 void main(){ printf("程序开始"); int s_fd,c_fd; //服务器和客户端的套接字标识符 int s_len,c_len; //服务器和客户端的消息长度 struct sockaddr_in s_addr; //服务器套接字地址 struct sockaddr_in c_addr; //客户端套接字地 int dataBytes=0;//读取消息长度 //消息 char sendbuf[BUFSIZ]; char recvbuf[BUFSIZ]; /***************************创建套接字**************************/ //socket函数,失败返回-1 //int socket(int domain, int type, int protocol); //第一个参数表示使用的地址类型,一般都是ipv4即AF_INET //第二个参数表示套接字类型:tcp一般用SOCK_STREAM数据流传输 //第三个参数设置为0 s_fd = socket(AF_INET,SOCK_STREAM,0); if(s_fd < 0){ perror("创建套接字失败"); return; } printf("创建套接字成功"); /****************************开启地址复用*******************/ int optval = 1;//1允许地址重用0禁止 int optlen = sizeof(optval); setsockopt(s_fd,SOL_SOCKET,SO_REUSEADDR,&optval,optlen); /********************初始化服务器套接字*****************/ //htons和htonl将端口和地址转成网络字节序 //定义服务器中地址中的域,AF_INET指IPv4 s_addr.sin_family = AF_INET; //定义服务端的套接字端口 s_addr.sin_port = htons(PORT); //定义套接字地址 //ip可是本服务器的ip,用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址 s_addr.sin_addr.s_addr = htonl(INADDR_ANY); /****************绑定套接字设置的端口号和IP***********************/ //对于bind,accept之类的函数,里面套接字参数都是需要强制转换成(struct sockaddr *) //bind三个参数:服务器端的套接字的文件描述符 s_len = sizeof(s_addr);//设置发送消息长度 if( bind(s_fd,(struct sockaddr*)&s_addr,s_len) < 0){ perror("绑定套接字失败"); return; } printf("绑定套接字成功"); /*****************设置服务器上的socket为监听状态*******************/ //监听,最大连接数10 if(listen(s_fd,10) < 0){ perror("监听失败"); return; } printf("监听成功端口: %d", PORT); while(1){ printf("等待连接..."); //fflush(stdin)刷新标准输入缓冲区,fflush(stdout)刷新标准输出缓冲区 fflush(stdout); //设置接收消息长度 c_len = sizeof(c_addr); /********************接收客户端连接请求*************************/ //调用accept函数后,会进入阻塞状态 //accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符, //s_fd和c_fd //s_fd仍然继续在监听状态,c_fd则负责接收和发送数据 //c_addr是一个传出参数,accept返回时,传出客户端的地址和端口号 //c_len是一个传入-传出参数,传入的是调用者提供的缓冲区的c_addr的长度,以避免缓冲区溢出。 //传出的是客户端地址结构体的实际长度。 //出错返回-1 c_fd = accept(s_fd,(struct sockaddr*)&c_addr,(socklen_t*)&c_len); if(c_fd < 0){ perror("accept失败"); continue; } printf("新连接:"); //inet_ntoa ip地址转换函数,将网络字节序IP转换为点分十进制IP //表达式:char *inet_ntoa (struct in_addr); printf("IP is %s", inet_ntoa(c_addr.sin_addr)); printf("Port is %d", htons(c_addr.sin_port)); printf("等待消息..."); while(1){ dataBytes=0; /*************************接收数据***********************/ printf("--------------------读取:"); fflush(stdout); dataBytes = recv(c_fd,recvbuf,BUFSIZ,0); if( dataBytes < 0){ perror("读取失败"); continue; }else if(dataBytes == 0){ printf("无消息"); }else printf("%s",recvbuf); //判断退出,quit,断开连接,关闭客户端 if(strncmp(recvbuf,"quit",4) == 0){ sprintf(recvbuf,"%s","EOF"); send(c_fd,recvbuf,sizeof(recvbuf)+1,0); //关闭连接 close(s_fd); printf("关闭服务器"); printf("程序结束"); return; } //EOF,断开连接 if(strncmp(recvbuf, "EOF",3) == 0){ break; } /************************发送数据*************************/ printf("--------------------发送:"); scanf("%s",sendbuf); send(c_fd,sendbuf,strlen(sendbuf)+1,0); //判断退出,quit,断开连接,关闭客户端 if(strncmp(sendbuf,"quit",4) == 0){ sprintf(sendbuf,"%s","EOF"); send(c_fd,sendbuf,sizeof(sendbuf)+1,0); //关闭连接 close(s_fd); printf("程序结束"); return; } //EOF,断开连接 if(strncmp(sendbuf,"EOF",3) == 0){ break; } }//while 收发消息 printf("断开连接"); }//while accept //关闭连接 close(s_fd); printf("程序结束"); return;}
客户端:
创建套接字文件skfd = socket()
主动向服务器发起链接请求,三次握手OK后即链接成功 connet(skfs..)
客户端向服务器发送数据 write(skfd);send(skfd)
客户端接收服务器数据 read(skfd);recv(skfd)
四次挥手断开链接,可以由任意方发起close(fd);shutdown(fd)
#include <sys/stat.h>#include <fcntl.h>#include <errno.h>#include <netdb.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <unistd.h> #define SERVER_PORT 6666 /*连接到服务器后,会不停循环,等待输入,输入quit后,断开与服务器的连接*/ int main() { //客户端只需要一个套接字文件描述符,用于和服务器通信 int clientSocket; //描述服务器的socket struct sockaddr_in serverAddr; char sendbuf[200]; char recvbuf[200]; int iDataNum; if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); //指定服务器端的ip,本地测试:127.0.0.1 //inet_addr()函数,将点分十进制IP转换成网络字节序IP serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(clientSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) { perror("connect"); return 1; } printf("连接到主机..."); while (true) { printf("发送消息:"); scanf("%s", sendbuf); printf(""); send(clientSocket, sendbuf, strlen(sendbuf), 0); if (strcmp(sendbuf, "quit") == 0) break; printf("读取消息:"); recvbuf[0] = \'\'; iDataNum = recv(clientSocket, recvbuf, 200, 0); recvbuf[iDataNum] = \'\'; printf("%s", recvbuf); } close(clientSocket); return 0; }
以上流程中可以看到服务器在与客户端通信过程中的收发数据使用的是新的文件描述符fd,而客户端收发数据使用的是创建socket时的描述符skfd,这里服务器是需要支持多客户端链接;即每个客户端与服务器进行链接后服务器都会创建一个新的文件描述符fd,用于单独和该客户端进行通信。
文章来源:网络转载,如有侵权请联系删除!评论处大家可以补充文章解释不对或欠缺的部分,这样下一个看到的人会学到更多,你知道的正是大家需要的。。。