SOCKET 基础API
1.01 创建套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
SOCKET socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
1.02 SOCKADDR_IN 地址结构体
SOCKADDR_IN socket_addr;
memset(&socket_addr, 0x0, sizeof(socket_addr)); //清零
socket_addr.sin_addr.S_un.S_addr = htonl(ADDR_ANY);//表示任何的ip过来连接都接受
socket_addr.sin_family = AF_INET;//使用IPV4地址
socket_addr.sin_port = htons(1234);//端口
1.03 绑定套接字与网络地址
用 TCP 进行网络通信,服务器端要经历几个状态,当调用 socket( ) 和 bind( ) 后,
虽然套接字已经建立起来,但是其状态是 CLOSED 即关闭状态。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));
1.04 listen创建套接口并监听申请的连接
backlog 用来描述 sockfd 的等待连接队列能够达到的最大值:
内核会在自己的进程空间里维护一个队列,这些连接请求就会被放入一个队列中,服务器进程会按照先来后到的顺序去处理这些连接请求,
这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限,这个 backlog 参数告诉内核使用这个数值作为队列的上限。
而当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。
int listen(int sock, int backlog);
对于服务器端来说,套接字已经建立起来,但是其状态是 CLOSED 即关闭状态。要被动打开。
listen(socket_fd, 100);
1.05 accept监听等待连接
accept是一个阻塞函数,会进入到监听状态,等待客户端的连接请求。
当客户与服务器连接( connect )并且连接成功(三次握手)后 accept 则返回客户端的套接字。
struct sockaddr *addr 为客户端的 IP 地址、端口信息。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
1.06 客户端连接服务器
连接目的主机(服务器)的指定端口,以便和目的主机 ( 在 access 中等待 ) 进行通信
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockaddr_in svr_addr = {};
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(4567);
svr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&svr_addr, sizeof(sockaddr_in));
1.07 接收数据
参数 flags 可指定一些标志,用于控制如何接收数据,一般设置为 0。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
1.08 发送数据
通过 flags 指定一些标志,来改变处理传输数据的方式, 一般设置为 0。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
1.09 inet_pton()与inet_ntop()
将点分文本的IP地址转换为二进制网络字节序的IP地址,而且inet_pton和inet_ntop这2个函数能够处理ipv4和ipv6。
int inet_pton(int af, const char *src, void *dst);
struct in_addr addr;
inet_pton(AF_INET, "192.168.1.222", &addr);
printf("ip addr: 0x%x\n", addr.s_addr);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
struct in_addr addr;
addr.s_addr = 0xde01a8c0;
char buf[20] = {0};
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
printf("ip addr: %s\n", buf);
1.10 字节序转换htonl、htons、ntohl、ntohs
主机字节序转网络字节序
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
网络字节序转主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
1.11 close、shutdown关闭套接字
close会将套接字描述符的引用计数减1,如果引用计数仍大于0,则不会引起TCP的四次挥手终止序列。
int close(int fd);
SHUT_RD:关闭读端
SHUT_WR:关闭写端
SHUT_RDWR:同时关闭读写端
int shutdown(int sockfd, int how);
2.01 UDP 发送函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
2.02 UDP 接收函数
写一个长度为0的数据报是可行的。在UDP情况下,这会形成一个只包含一个IP首部(对于IPv4通常为20字节,对于IPv6通常为40字节)和一个8字节UDP首部而没有数据的IP数据报。
这也意味着对于数据报协议,recvfrom返回0值是可接受的。
它并不像TCP套接字上read返回0值那样表示对端已关闭连接。
既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类的事情。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
3.01 Windows控制套接字的 I/O 模式
ioctlsocket函数主要是用来设置或取消非阻塞套接字的。
将套接字设置为非阻塞式套接字后,connect、send和recv等函数将变成非阻塞式的,调用后会立即返回,执行的操作结果成功与否,需要通过后续代码去判断。
unsigned long unblock = 1;
int ret = ioctlsocket(tSock, FIONBIO, (unsigned long *)&unblock);
3.02 Linux控制套接字的 I/O 模式
Int setsockopt( int sockfd, int level, int optname, const void* optval, socklen_t optlen);
Int getsockopt(int sockfd, int level, int optname, void* optval, socklen_t optlen);
// 设置发送缓冲区大小
int optVal = 2*1024*1024; // 2MB
setsockopt(tSock, SOL_SOCKET, SO_SNDBUF, (char *)&optVal, sizeof(optVal));
// 设置接收缓冲区大小
int optVal = 2*1024*1024; // 2MB
setsockopt(tSock, SOL_SOCKET, SO_RCVBUF, (char *)&optVal, sizeof(optVal));
// 获取实际发送长度
int sent_bytes;
getsockopt(tSock, IPPROTO_TCP, TCP_INFO, &sent_bytes, sizeof(optVal);
// 设置端口复用
setsockopt(tSock,SOL_SOCKET,SO_REUSEADDR,&buf,1));
4.01 select多路复用
#include <sys/select.h>
struct timeval{
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
void FD_ZERO(fd_set *fdset); // 清除fdset的所有位
void FD_SET(int fd,fd_set *fdset); // 打开fdset中的fd位
void FD_CLR(int fd,fd_set *fdset); // 清除fdset中的fd位
int FD_ISSET(int fd,fd_set *fdset); // 检查fdset中的fd位是否置位
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
// 定义集合
fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;
// 清理集合
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);
// 将描述符(socket)加入集合
FD_SET(_sock, &fdRead);
FD_SET(_sock, &fdWrite);
FD_SET(_sock, &fdExp);
SOCKET maxSock = _sock;
// select 函数不设置 timeval 时,select是阻塞的,直到有数据。
// select 函数设置 timeval 时,超时即返回、select是非阻塞的。
timeval t = { 1,0 };
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
// 判断描述符(socket)是否在集合中
if (FD_ISSET(_sock, &fdRead)){}
5.01 ioctl()函数简介