Socket网络编程流程详解(函数调用及TCP状态转移)

一.Socket

1.1 介绍

  • Socket(套接字)是一种进程间通信方式,和其他进程间通信方式(管道、共享内存等)不同的是Socket可以在不同主机之间的进程通信
  • 一个完整的socket使用唯一的五元组进行标识:{协议、本地地址、本地端口、远程地址、远程端口}

1.2 socket数据结构

  • 用于ip地址与端口管理
struct sockaddr {
    unsigned short sin_family;  /*地址族*/
    char sa_data[14];           /*14 字节的协议地址,包含该 socket 的 IP 地址和端口号。 */
};
struct in_addr {
    unsigned long int s_addr;   /* 32 位 IPv4 地址,网络字节序 */
};
struct sockaddr_in {
    short int sin_family;        /*地址族*/
    unsigned short int sin_port; /*端口号*/
    struct in_addr sin_addr;     /*IP 地址*/
    unsigned char sin_zero[8];   /*填充 0 以保持与 struct sockaddr 同样大小*/
};
#include <netinet/in.h>
//sin_family:  AF_INET-->IPv4协议  AF_INET6-->IPv6协议

1.3 socket编程系统函数

1.3.1 网络字节序与主机字节序的转化

  • 端口转换
 #include <arpa/inet.h>
uint16_t htons(uint16_t host); // Host to Network Long
uint32_t htonl(uint32_t host); // Network to Host Long
uint16_t ntohs(uint16_t net);  // Host to Network Short
uint32_t ntohl(uint32_t net);  // Network to Host Short
  • ipv4地址转换
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// IPv4 的函数原型:
// 点分十进制数的IP地址字符串转32位的网络字节序的二进制值
// 成功返回1,不成功返回0
int inet_aton(const char *straddr, struct in_addr *addrptr);

// 功能同inet_aton
// in_addr_t就是unsigned long int 代表s_addr
in_addr_t inet_addr(const char *straddr); 
in_addr_t inet_network(const char *straddr); 

// 32位的网络字节序的二进制值转IP地址字符串
char *inet_ntoa(struct in_addr inaddr); 
  • ipv6地址转换(兼容ipv4)
#include <arpa/inet.h>
/**
 * @brief  This function converts the character string src into a network address structure
           in the af address family
           函数 inet_pton 跟 inet_aton 实现的功能类似,只是多了 family 参数
 * @param family  AF_INET表示 IPv4 协议,AF_INET6表示 IPv6 协议
 * @param src
 * @param dst
 * @return 成功返回1,src无效返回0,af无效返回-1
 */
int inet_pton(int family, const char *src, void *dst);
/**
 * @brief 函数 inet_ntop 跟 inet_ntoa 类似,其中 len 表示表示转换之后的长度(字符串的长度)
 * @param af  AF_INET表示 IPv4 协议,AF_INET6表示 IPv6 协议
 * @param src
 * @param dst
 * @return 成功返回指向dst的非空指针
 */
const char *inet_ntop(int af, const void *src, char *dst, socklen_t len);

1.3.2 客户端/服务端编程流程函数

#include <sys/socket.h>
#include <sys/types.h>
/**
 * @brief 申请套接字
 * @param domain 域名:一般有如下类型
                AF_UNIX: Local communication
                AF_INET: IPv4 Internet protocols
                AF_INET6: IPv6 Internet protocols
 * @param type  指明连接语义:SOCK_STREAM 字节流TCP、SOCK_DGRAM 数据报UDP
 * @param protocol  指定 socket 所使用的传输协议编号,通常为0
 * @return file descriptor,失败返回-1
 */
int socket(int domain, int type, int protocol);

/**
 * @brief 绑定IP地址与端口
 * @param sockfd 套接字
 * @param my_addr   
 * @param addrlen  
 * @return 成功则返回 0,失败返回-1
 */
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

/**
 * @brief 监听端口
 * @param sockfd 套接字
 * @param backlog 指定同时能处理的最大连接要求,通常为 10 或者 5。最大值可设至 128   
 * @return 成功则返回 0,失败返回-1
 */
int listen(int sockfd,int backlog);

/**
 * @brief 监听端口
 * @param sockfd 套接字
 * @param addr为结构体指针变量,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中
 * @param addrlen表示结构体的长度,为整型指针
 * @return 成功则返回连接fd,失败返回-1
 */
int accept(int sockfd, struct sockaddr * addr, int * addrlen);

/**
 * @brief 从fd中接收信息,只是把数据从内核缓冲区复制到了应用缓冲区. 数据的接收是靠内核(TCP协议栈)完成的
 * @param sockfd 套接字
 * @param buf 缓冲区
 * @param len 缓冲区的长度
 * @param flags MSG_DONTROUTE--不查找路由表
                MSG_OOB--接受或发送带外数据
                MSG_PEEK--查看数据,并不从系统缓冲区移走数据
                MSG_WAITALL--等待任何数据
 * @return 成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1
 */
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// 和recv的区别是 src_addr接收对端的地址信息
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

/**
 * @brief 对fd中发送信息,只是把数据从应用层缓冲区复制到了内核缓冲区, 数据的发送时靠内核(TCP协议栈)完成的
 * @param sockfd 套接字
 * @param buf 缓冲区
 * @param len 缓冲区的长度
 * @param flags 同上,具体可以查阅man文档,man send
 * @return 成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1,缓冲区满返回-1
 */
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 若文件顺利关闭则返回 0,发生错误时返回-1
#include <unistd.h>
int close(int fd);

/**
 * @brief 通过ip地址及端口连接服务器
 * @param sockfd 套接字
 * @param serv_addr 结构体指针变量,存储着远程服务器的 IP 与端口号信息
 * @param addrlen   结构体变量的长度
 * @return 成功则返回1 失败返回-1
 */
#include <sys/socket.h>
int connect(int sockfd,struct sockaddr *serv_addr,int addrlen);

1.3.3 对socket的设置

#include <sys/socket.h>
#include <sys/types.h> 
// 成功则返回 0,失败返回-1
int getsockname(int peerfd, struct sockaddr *addr, socklen_t *addrlen); //解析本端IP和port
int getpeername(int peerfd, struct sockaddr *addr, socklen_t *addrlen); //解析对端IP和port

/**
 * @brief 获得设置信息和设置socket所引用的文件描述符sockfd,必须要放在 bind 之前调用
 * @param sockfd 套接字
 * @param level  选项定义的层次;支持 SOL_SOCKET、 IPPROTO_TCP、 IPPROTO_IP 和 IPPROTO_IPV6
 * @param optname 需设置的选项
 * @param optval  指针,指向存放选项值的缓冲区
 * @param optlen  缓冲区长度
 * @return 成功则返回1 失败返回-1
 */
int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
               const void *optval, socklen_t optlen);
  • 一些具体的操作如下:
// 如果在已经处于 ESTABLISHED 状态下的 socket(一般由端口号和标志符区分)
// 调用 close(socket)(一般不会立即关闭而经历 TIME_WAIT 的过程)后想继续重用该 socket
int reuse=1;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&reuse,sizeof(int));

// 如果要已经处于连接状态的 socket 在调用 close(socket) 后强制关闭,不经历 TIME_WAIT 的过程
int reuse=0;
setsockopt(s,SOL_SOCKET ,SO_DONTLINGER,(const char*)&reuse,sizeof(int));

// 对端口的复用(意味着linux内核可以在同一台物理主机上实现系统级别的负载均衡)
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(int));

// 在 send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限
int nNetTimeout=1000; // 1 秒
// 发送时限
setsockopt(socket, SOL_S0CKET,SO_SNDTIMEO, (char *)&nNetTimeout,sizeof(int));
// 接收时限
setsockopt(socket, SOL_S0CKET,SO_RCVTIMEO, (char *)&nNetTimeout,sizeof(int));
• SO_REUSEPORT使用场景:linux kernel 3.9 引入了最新的SO_REUSEPORT选项,使得多进程或者多线程创建多个绑定同一个ip:port的监听socket,提高服务器的接收链接的并发能力,程序的扩展性更好;此时需要设置SO_REUSEPORT
• SO_REUSEADDR的使用场景:当服务端出现timewait状态的链接时,确保server能够重启成功

1.3.4 对文件描述符fd的设置

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

// 例如设置fd使用非阻塞IO
int flags = fcntl(fd, FD_GETFL, 0);
int ret = fcntl(fd, FD_SETFL, O_NONBLOCK | flags);

二.函数调用及TCP状态转移

  • 简易同步操作的服务端和客户端的函数调用流程及TCP转移
  • 1.关于TCP连接的全连接/半连接队列可以参考:TCP的全连接和半连接队列
  • 2.蓝色为TCP状态变化
    在这里插入图片描述

三.参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值