sylar-from-scratch---socket模块

109 阅读8分钟

image.png 套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,是不同主机之间的进程进行双向通信的约定,使用套接字中的相关函数来完成通信过程。

IP号与端口号

  • IP用来识别是哪一台主机,端口号用来识别是主机中的哪一个进程,IP+端口号能识别网络上的某一台主机的某一个进程。
  • 一个进程可以绑定多个端口号,一个端口号只能绑定一个进程。
  • IP有源IP和目的IP之分,端口号也有源端口号和目的端口号。

TCP与UCP

  • TCP为传输控制协议,面向连接,是一种可靠传输的面向字节流的通用协议。
  • UDP为用户数据报协议,无连接,不提供差错恢复,不能提供数据重传,是一种不可靠传输。

socket API

服务器端:socket-->bind-->listen-->accept-->read/write-->close
客户端:socket-->connect-->read/write-->close

socket

创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
domain:选择创建的套接字所用的协议簇,包括AF_INET-IPv4协议、AF_INET6- IPv6协议、AF_LOCAL-Unix域协议、AF_ROUTE-路由套接口、AF_KEY-密钥套接口;
type:指定套接口类型,所选类型有SOCK_STREAM-字节流套接字(TCP)、SOCK_DGRAM-数据报套接字(UDP)、SOCK_RAW-原始套接口;
procotol:使用的特定协议,一般使用默认协议(NULL)。

bind

服务端将用于通信的地址和端口(socketaddr_in结构体)绑定到socket上。 int bind(int socket, const struct sockaddr *address, socklen_t address_len);
socket:未作连接的套接字描述符(套接字号);
address:指向特定协议的地址指针;
address_len:地址结构的长度;
返回:没有错误返回0,否则SOCKET_ERROR。

listen

将套接字文件描述符从主动转为被动文件描述符,然后用于被动监听(当没有客户端请求时套接字处于睡眠状态)客户端的连接。
int listen(int socket, int backlog);
socket:socket创建的未连接的套接字文件描述符;
backlog:所监听的端口队列的大小,该队列用于记录正在连接但还未连接完成的客户端(正在等待完成三次握手过程)+已完成的连接队列(已完成相应的TCP三次握手),一般设置队列容量为2、3即可,队列的最大容量需要小于30。
返回值:成功返回为0,失败返回-1,设置errno。

  • 连接请求只能由客户端发起,服务端的listen函数将服务端的主动文件描述符(主动向对方发送数据)转为被动描述符(只能被动地等待别人主动发送数据,再回应),否则无法用于监听客户端的连接。
  • 主动套接字用来发起连接connect,被动套接字用来接收连接accept。

accept

当套接字处于监听状态时,可以通过accept()函数来接收客户端请求,允许在套接字上进行传入连接尝试。 int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

  • 参数与connect()和listen()函数相同。
  • 从已完成连接的队列中返回第一个连接,若已完成队列为空,则阻塞;填充对方地址和信息;成功返回已经连接套接字并变为主动套接字。
  • accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。 后面再通信时用新生成的套接字,而不是服务器原来的套接字。
  • accept()会阻塞程序执行(后面代码不能执行),直到有新的请求到来。

connect

当客户端调用connect函数之后,只有当服务器端连接请求或者发生断网的异常情况而中断连接请求时connect函数才会返回,完成函数的调用。
connect函数在返回后并不进行数据交换,而是要等服务器端accept之后才能进行数据交换(read\write);因为服务器accept连接请求只是把连接请求的信息记录到等待队列中。
int connect(int socket, const struct sockaddr *addr, socklen_t addrlen);
socket:socket文件描述符;
addr:指定服务器端地址信息,包含IP地址和端口号;
addrlen:传入sizeof(addr)的大小;
返回值:成功则返回0,失败返回-1,设置errno。

send

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
int send( SOCKET s,char *buf,int len,int flags );
s:指定发送端套接字描述符;
buf:存放应用程序要发送数据的缓冲区;
len:实际要发送的数据的字节数;
flags:一般设置为0;
调用该函数时,send先比较待发送数据的长度len与s的发送缓冲区长度;len>s的发送缓冲区长度,返回SOCKET_ERROR,len<s的发送缓冲区长度,先检查协议是否在发送缓冲中的数据,若是等协议将数据发送完,否则将buf中的数据copy到s的剩余空间中,copy成功,返回copy的字节数,若copy出现错误或传送过程中网络出现错误,返回SOCKET_ERROR。

recv

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
int recv( SOCKET s , char FAR * buf , int len , int flags );
buf:指明一个缓冲区,用来存放recv函数接收到的数据;
len:指明buf的长度;

  • 应用程序调用recv函数时,recv先等待s的发送缓冲区中的数据被协议发送完毕,若协议在发送时出现网络错误,返回WOCKET_ERROR;
  • 若s的发送缓冲区没有数据或数据被协议发送完毕,recv函数先检查s的接收缓冲区,若正在接收数据,一直等待直到接收完毕;
  • 协议将数据接收完毕后,recv函数将s中的接收缓冲区中的数据copy到buf中,返回copy的字节数;若buf的长度小于接收的数据,要调用几次recv函数才能将s的接收缓冲区数据copy到buf中;
  • 若copy出错,返回SOCKET_ERROR,若在等待协议接收数据时网络中断,则返回0。

sendto\recvfrom

sendto函数 和 recvfrom 函数一般用于UDP协议中,但是如果在 TCP 中 connect 函数调用后也可以用,即利用数据报文方式进行数据传输 。在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。 int sendto(int sockfd, const void *msg,int len , unsigned int flags, const struct sockaddr *to, int tolen);
to:表示目的主机的IP和端口号信息;
tolen:设置为sizeof(struct sockaddr);
返回值:

  • 小于0——即返回-1,判断errno。如果errno为 EAGAINE 或 EWOULDBLOCK ,表示当前缓冲区写满,可以继续写, 或者等待epoll或select的后续通知,一旦发送缓冲区由满变为不满,就会触发写操作。如果errno为EINTR ,表示被中断了,可以继续写,或者等待epoll或select的后续通知。 否则真的出错了,即 errno 不为 EAGAINE 或 EWOULDBLOCK 或 EINTR,此时应该close(sockfd)。
  • 大于等于0且不等于要发送的长度——继续send直到发送发送完毕。 int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen);
    from:保存源主机的IP地址和端口号信息;
    fromlen:常设置为sizeof(struct sockaddr); 返回值:
  • 0——表明对端已经关闭连接,需要自己关闭连接,即close(fd),此时会用select或epoll事件做触发。若为select,使用FD_CLR(sockfd,fd_set)将sockfd清除掉,不再监听;若为epoll,系统自己会清除sockfd,不再监听。
  • 大于0且小于sizeof(buffer)——表示数据肯定读完,若等于sizeof(buffer)说明数据可能还没有读完,需要继续读。
  • 小于0——如果 errno 为 EAGAINE 或 EWOULDBLOCK /* Operation would block */ 表示暂时无数据可读,可以继续读,或者等待epoll或select的后续通知。如果 errno 为 EINTR 示被中断了,可以继续读,或者等待epoll或select后续的通知。否则,真的是读取数据失败。(此时应该close(sockfd))。

getsockname

用于获取与某个套接字关联的本地协议地址
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);

getpeername

用于获取与某个套接字关联的外地协议地址,可以在TCP的服务器端accept成功后,通过该函数来获取当前连接的客户端的IP地址和端口号。 int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);

getsockopt/setsockopt

获取/设置套接字选项。 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int socket, int level, int optname, const void *optval, socklen_t optlen); sockfd:文件描述符;
level:协议层次(SOL_SOCKET为套接字层次、IPPROTO_IP为ip层次、IPPROTO_TCP为TCP层次);
optname:选项的名称(套接字层次),SO_BROADCAST 是否允许发送广播信息、SO_REUSEADDR 是否允许重复使用本地地址、SO_SNDBUF 获取发送缓冲区长度、SO_RCVBUF 获取接收缓冲区长度 、SO_RCVTIMEO 获取接收超时时间、SO_SNDTIMEO 获取发送超时时间。
optval:获取到的选项的值;
optlen:value的长度。
返回值:成功为0,-1为失败。 image.png blog.csdn.net/Z_Stand/art… blog.csdn.net/weixin_4757… xiaoxiami.gitbook.io/linux-serve…