Socket网络编程

一直听说了socket网络编程,但是没有是深入了解过是什么东东。今天来写个学习小结

socket的神秘面纱

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。这段话是百度到的官方的话,听着确实没有什么感觉。

来看一张图(图片来自于https://www.cnblogs.com/zengzy/p/5107516.html)
在这里插入图片描述
从图中,我们可以知道socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。较为详细的socket的前世今生可以看看这篇文章

怎么使用socket

首先来看几个socket提供的库函数。
socket接口

1. int socket(int protofamily, int so_type, int protocol)

这是实例化一个socket的接口函数,接口参数分别如下:

protofamily 指协议族,常见的值有:
AF_INET,//指定so_pcb中的地址要采用ipv4地址类型
AF_INET6,//指定so_pcb中的地址要采用ipv6的地址类型
AF_LOCAL/AF_UNIX,//指定so_pcb中的地址要使用绝对路径名

so_type 指定socket的类型,比较常用的类型有:
SOCK_STREAM:对应tcp
SOCK_DGRAM:对应udp
SOCK_RAW:自定义协议或者直接对应ip层

protocol 指定具体的协议,也就是指定本次通信能接受的数据包的类型和发送数据包的类型,常见的值有:
IPPROTO_TCP,TCP协议
IPPROTO_UDP,UPD协议
0,如果指定为0,表示由内核根据so_type指定默认的通信协议

bind接口

2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//将创建的socket和server的IP 地址以及端口绑定

其中三个参数分别代表

sockfd 是调用socket()函数创建(实例化的)的socket描述符
addr 是具体的地址(一般是server中奖本地的IP地址与端口绑定到实例化的socket上)
addrlen 表示addr的长度

bind一般是用在server上,将本地IP地址以及端口绑定到socket上。

connect 接口

3 . int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect 是client向sever请求连接的函数(一般是使用TCP协议的时候),这是个很重要的接口,等会debug的时候可以使用wireshark看看调用这个接口的时候发生了什么。

listen接口

4. int listen(int sockfd, int backlog);

listen接口一般是在server上,用于监听有没有连接到本地初始化的socket(本地的IP地址以及端口)上,backlog是监听的连接数(我这里理解为外部client可以同时与server通信的最大数量,有问题以后再来改)

accept接口

5. int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);

这也是个很重要的接口。从后面两个参数的类型可以看到,这个很可能是一个可以传出的参数,当然了,调用了这个函数以后,TCP连接就真正的建立起来了,因此可以知道addr里面装的肯定是client的IP地址以及端口号。

在使用listen函数告知内核监听的描述符后,内核就会建立两个队列,一个SYN队列,表示接受到请求,但未完成三次握手的连接;另一个是ACCEPT队列,表示已经完成了三次握手的队列。而accept函数就是从ACCEPT队列中拿一个连接,并生成一个新的描述符,新的描述符将由addr中的IP地址和端口初始化。(个人理解)

connect、listen、accept的流程
以AF_INET,SOCK_STREAM,IPPROTO_TCP三个参数实例化的socket为例,通过一个副图来讲解这三个函数的工作流程及粗浅原理。
在这里插入图片描述
图片来自于https://www.cnblogs.com/clschao/articles/9585555.html

  1. 服务器端在调用listen之后,内核会建立两个队列,SYN队列和ACCEPT队列,其中ACCPET队列的长度由backlog指定。
  2. 服务器端在调用accpet之后,将阻塞,等待ACCPT队列有元素。
  3. 客户端在调用connect之后,将开始发起SYN请求,请求与服务器建立连接,此时称为第一次握手。
  4. 服务器端在接受到SYN请求之后,把请求方放入SYN队列中,并给客户端回复一个确认帧ACK,此帧还会携带一个请求与客户端建立连接的请求标志,也就是SYN,这称为第二次握手
  5. 客户端收到SYN+ACK帧后,connect返回,并发送确认建立连接帧ACK给服务器端。这称为第三次握手
  6. 服务器端收到ACK帧后,会把请求方从SYN队列中移出,放至ACCEPT队列中,而accept函数也等到了自己的资源,从阻塞中唤醒,从ACCEPT队列中取出请求方,重新建立一个新的sockfd,并返回。

这就是listen,accept,connect这三个函数的工作流程及原理。从这个过程可以看到,在connect函数中发生了两次握手。(等会就来上证据)

tcp的send接口
以下是摘抄的别人的理解(^ - ^)(https://www.cnblogs.com/clschao/articles/9585555.html)

send函数只负责将数据提交给协议层。 当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR; 如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据; 如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len; 如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。 如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR; 如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。 如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)

tcp的recv接口

recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0 。对方优雅的关闭socket并不影响本地recv的正常接收数据;如果协议缓冲区内没有数据,recv返回0,指示对方关闭;如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭。

send和recv的缓存区设置

既然提到了send和recv的大致原理,那么担心大家会问如何设置缓冲区大小啊,那么在这里做一下解释
通过setsockopt设置SO_SNDBUF、SO_RCVBUF这连个默认缓冲区的值,再用getsockopt获取设置的值。

socket的工作流程

server上的主要几个函数

SOCKET          sServer;        //Server socket  
SOCKET          sClient;        //Client socket  
SOCKADDR_IN     addrServ;;      //Serevr address
char            buf[BUF_SIZE];  //Receiving data buffer area   
char            sendBuf[BUF_SIZE];//Return data to client
int             retVal;         //Return value 

sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //初始化一个socket实例,IP协议为IPV4,传输协议为TCP,

addrServ.sin_family = AF_INET;  
addrServ.sin_port = htons(4999);  
addrServ.sin_addr.s_addr = INADDR_ANY;      //设置为本地IP地址    

retVal = bind(sServer, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));  //将创建的socket 和本机的IP 地址以及端口绑定

retVal = listen(sServer, 1);  //开始在创建的socket 上监听连接的到来,监听的连接数为1

sClient = accept(sServer,(sockaddr FAR*)&addrClient, &addrClientlen);  //此处会一直阻塞,直到有连接完成,
//当accept返回后,以为着TCP的三次握手连接已经完成了。

retVal = recv(sClient, buf, BUF_SIZE, 0);  //revc函数返回的是实际copy到buf中的字节数

send(sClient, sendBuf, strlen(sendBuf), 0);

client上的主要几个函数

WSADATA wsd; //WSADATA Variable  
SOCKET sHost; //Server socket
SOCKADDR_IN servAddr; //Server Address  
char buf[BUF_SIZE]; //Receiving data buffer area 
char bufRecv[BUF_SIZE];  
int retVal; //return value 

sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //初始化一个socket实例,IP协议为IPV4,传输协议为TCP,

servAddr.sin_family =AF_INET;  
servAddr.sin_addr.s_addr = inet_addr("10.76.26.90");  
servAddr.sin_port = htons((short)4999);  
int nServAddlen  = sizeof(servAddr);  

retVal=connect(sHost,(LPSOCKADDR)&servAddr, sizeof(servAddr));  //将创建的实例化的socket连接到服务器的IP地址上 ,
//TCP的三次握手开始了

retVal = send(sHost, buf, strlen(buf), 0);  

recv(sHost, bufRecv,BUF_SIZE , 0);

让我们用wireshark看看在server和client发生了什么哦。
首先先运行server然后再运行client。
首先server上运行到accept函数的时候会进入阻塞状态,如下图
在这里插入图片描述
看看server上的wireshark中有什么东东木有。
在这里插入图片描述
空空如也哦。
然后运行到client的connect函数这里,也看看client中的wireshark,发现里面也是空空如也。
在这里插入图片描述
OK,client把connect函数运行完,再看看wireshark中有什么东西。
在这里插入图片描述
再看看server中的wireshark有什么东西。
在这里插入图片描述
同时server中的accept函数也已经走完了
在这里插入图片描述
在这里插入图片描述
所以从这里我们就可以看到当client中的connect开始调用以后,就开始了TCP的三次握手,当client中的connect返回以后,TCP的三次握手才完成。而且accept走完后,客户端的IP地址以及端口号都已经得到了,这里这个41198和真正的端口号61008有点不一样,前面server的端口号在VS中debug时看到的数据是34579,也不一样,不知道是什么原理。

发送一帧数据
在client中输入了imaclientareyouok?,然后使用send接口发送,看看wireshark中有什么东西。
在这里插入图片描述
可以看到server已经完成接收了,而且字节数刚好是18,说明没有数据被丢掉。此时数据并没有被server中的函数copy到buf中,说明底层的通信协议已经完成了数据的接受与发送了。
嗯。。。就先这样子吧,以后有问题再补充,。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值