- 套接字:是一种通信机制,通过这种机制,客户/服务器系统的开发工作既可以在本机可以络进行。 注:套接字的创建和使用与管道是有区别的,套接明确的将客户和服务器区分开来,套接字可以实现将多个客户连接到一个服。
- 套接字的连接:你可以把套接字连接想象为一客人找餐馆。顾客提前给美食指南客服打电话,客服接听电话,它转到符合客户要求的一些餐馆(服务器进程),然后再从那里转到客户要点的具体菜的餐馆前台(服务器套接字)。每个进入的电话呼叫(客户)都被转到正确终节点,而中间介入的客服则可以空出来处理后续的电话。 服务器端连接的顺序:
- (1)socket():创建套接字,服务器应用程序用系统调用socket来创建,它是系统分配给该服务器进程的类似文件描述符的资 源,它不能与其他进程共享。 (2)bind():给创建的套接字起名字。本地套接字的名字是Linux文件系统中的文件名,网络套接字的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点)。命名完之后服务器进程就开始等待客户连接到这个命名的套接字。 (3)listen():监听,创建一个队列并将其用于存放来自客户端的进入连接。 (4)accept():接受客户的连接。注:服务器调用accept时,它会创建一个与原有的命名套接字不同的新套接字。这个套接字只能用于这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自其他客户的连接。对于一个简单的服务器来说,后续的客户将在监听队列中等待,直到服务器再次准备就绪。 (5)recv()、send():建立连接之后客户端和服务器端开始你一句我一句的聊天。 (6)close():用完一个套接字后,就应该把它删掉,(客户端和服务器端都应该关掉)即使是在程序因接收到一个信号而异常终止的情况下也应该这么做,这可以避免文件系统应充斥着无用的文件而变得混乱。 客户端创建连接的顺序: (1)socket()、(2)connect(): 客户端调用一个未命名套接字和服务器监听套接字之间建立连接的方法连接到服务器就是connect来实现的,要想建立连接就要先进性三次握手,等三次握手完成之后就请求服务器端accept接收连接。
- 套接字的属性:域(domain)、类型(type)和协议(protocol)。 (1):套接字的域:域指定套接字通信中使用的网络介质。最常见的套接字域书AF_INET,它指的是Internet网络。 注:一般情况下,小于1024的端口号都是为系统服务保留的,并且服务的进程必须具有超级用户权限。
(2):套接字类型:一个套接字域可能有多种不同的通信方式,而每种通信方式又有其不同的特性,但是AF_UNIX(是一台还未联网的计算机上的套接字)域的套接字没有这样的问题,它们提供了一个可靠的双向通信路径。在网络域中就需要注意底层网络的特性,以及不同的通信机制是如何受到它们的影响的。因特网协议提供了两种通信机制:流、数据报。 1)流套接字:它的行为是可预见的,它提供的是一个有序、可靠、双向字节流的链接。发送的数据可以确保不丢失、复制或乱序到达,并且再这一过程中发生的错误也不会显示出来。大的消息将被分片、传输、再重组。流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP链接实现的。TCP/IP代表的是传输控制协议/网际协议。IP协议是针对数据包的底层协议,它提供从一台计算机通过网络到达另一台计算机的路由。TCP协议提供排序、流控和重传,以确保大数据的传输可以完整地到达目的地或报告一个适当的错误条件。 2)数据报套接字:与流套接字相反,由SOCK_DGRAM指定的数据包套接字不建立和维持一个链接,可以对发送的数据包长度有限制,数据报可能会丢失、复制或乱序到达。数据报套接字是在AF_INET中通过UDP/IP连接实现的,它提供的是一种无序的、不可靠的服务。从资源角度看,相对来说它们开销较小,因为不需要维持网络连接。因为不需要花费时间来建立连接,所以它们的速度也很快。优点:服务器的崩溃不会给客户造成不便,也不会要求客户重启,因为基于数据报的服务器通常不保留连接信息,所以它们可以再不打扰其客户的前提下通知并重启。
-
代码部分:服务器端:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { //创建套接字,并返回一个描述符,该描述符可以访问套接字 //AF_INET是IPV4和ARPA因特网指定的协议 //第二个参数是通信类型,而此处SOCK_STREAM(流服务),对AF_INET域套接字来说它默认是通过TCP连接来提供这一特性 //第三个参数是指定使用的协议,但是通信所使用的协议一般是由套接字的域和套接字类型来决定,通常不需要选择 int sockfd = socket(AF_INET,SOCK_STREAM,0); assert(sockfd != -1); //ipv4专用的 struct sockaddr_in saddr, caddr; //该结构体指定了自己的ip地址和端口 memset(&saddr,0,sizeof(saddr)); //将socket置空 //在AF_INET域种,套接字的地址由结构sockaddr_in来指定,头文件netinet/in.h saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); //<1024 root saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//自己的ip地址是多少就写多少,用ifconfig查,如果没有192...就用172.0.0.1, //命名套接字作用:通过socket调用创建的套接字可以被其他进程使用 listen(sockfd,5); //创建监听队列,这种机制允许当服务器程序正忙于处理前一个客户请求的时候,将后续的客户连接放入队列等待处理,backlog参数通常是5,举例:假设只有一个门迎,门迎接客之后交接给店内的服务员然后再去迎接下一位客人。 while(1) { int len = sizeof(caddr); int c = accept(sockfd,(struct sockaddr*)&caddr,&len); //accept函数将创建一个新的套接字来与客户进行通信,并返回新的套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。 // 举例: accept门迎,c是新的服务员点餐,一个服务员只服务桌客人,每桌服务员会不一样,所以c每次从客户端接收的数据也不一样 c是你和饭店沟通的桥梁那么,c和客户端沟通的桥梁,每个c不一样 if(c < 0) { continue; } printf("accept(c = %d),ip:%s,port:%d\n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port)); char buff[128] = {0}; recv(c,buff,127,0); printf("buff = %s\n",buff); send(c,"ok",2,0); close(c); //一定要关闭它,结束套接字的连接 } }
客户端:
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> int main() { // int sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字, assert(sockfd != -1); struct sockaddr_in saddr; memset(&saddr,0,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(6000); saddr.sin_addr.s_addr = inet_addr("192.168.1.102");//指定的是服务器端的,客户端和服务器此处的ip必须一样才能链接成功 //(struct sockaddr*)&saddr强转,因为一般用的是通用的地址结构,而本次代码用的IPV4,所以改成IPV4的专用地址结构 int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr)); assert(res != -1); char buff[128] = {0}; printf("input:\n"); fgets(buff,128,stdin); send(sockfd,buff,strlen(buff),0);//先发送数据,然后清空buff,然后再将收到的数据存放在buff memset(buff,0,128); recv(sockfd,buff,127,0); printf("buff = %s\n",buff); close(sockfd); }