1. 理论基础
1.1 IPV4 socket 地址结构 (3大要素)
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;//地址族:在这里必须设为AF_INET(代表IPV4),AF_INET6(代表IPV6)
in_port_t sin_port; //端口: 0-65536 (2 bytes)
struct in_addr sin_addr; //IP地址:32位IPV4地址(4 bytes)
char sin_zero[8];
}
//通用地址结构,用来指定与socket关联的地址
struct sockaddr {
uint8_t sin_len; //整个sockaddr结构体的长度
sa_family_t sin_family;//指定该地址族
char sa_data[14];//由sin_family决定它的形式,这里字节为14,就是sockadd_in结构体后三个元素的长度
}
1.2 字节序
因为socket是支持异构通讯的,所以需要规定字节序,因为不同的硬件,其字节序不一致。
字节序分为:
- 小端字节序(高位地址保存高位字节,低位地址保存低位字节),如X86
- 大端字节序(高位地址保存低位字节,低位地址保存高位字节)
网络字节序即使用socket通讯人为规定的字节序为:大端字节序,所有通讯个体都必须转换成大端字节序发送
下面这段代码可以测试自己系统是小端还是大端
#include <stdio.h>
int main(void)
{
unsigned int x = 0x12345678;
unsigned char *p = (unsigned char *)&x;
printf("%0x %0x %0x %0x\n",p[0],p[1],p[2],p[3]);
//78 56 34 12 -> 小端字序
//12 34 56 78 -> 大端字序
return 0;
}
1.3 字节序转换函数
#include <arpa/inet.h>
uint32_t htonl (uint32_t hostlong);
uint16_t htons (uint16_t hostshort);
uint32_t ntohl (uint32_t hostlong);
uint16_t ntohs (uint16_t hostshort);
其中,h代表host, n代表network; s代表short, l代表long
如 htonl -> 代表将4字节整数由主机字节序(大端或小端)转换成网络字节序(大端)
ntohl 则反之
1.4 地址转换函数
由于用户对IP地址习惯了“点分十进制”的地址,如"192.168.0.100",但是程序并不是读这个地址,而是32位的地址,
因此需要用地址转换函数来方便用户编程
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton (const char *cp, struct in_addr *inp);//这个函数就也是将”点分十进制“地址转换成32位地址,
//只不过是转换成一个地址结构,而不是返回一个整形地址
in_addr_t inet_addr (const char *cp) //这个函数就是将”点分十进制“地址转换成32位地址
char *inet_ntoa (struct in_addr in); //将地址结构转换成”点分十进制“地址
举例:
int main(void)
{
unsigned long addr = inet_addr ("192.168.0.100");//将”点分十进制“地址转换成32位地址
printf("addr = %u\n", ntohl(addr));//将32位地址以主机字节序输出
struct in_addr ipaddr;
ipaddr.s_addr = addr;
printf("%s\n", inet_ntoa(ipaddr));//将地址结构转换成”点分十进制“地址,应该打印”192.168.0.100“
return 0;
}
1.5 socket 类型
SOCK_STREAM: tcp
SOCK_DGRAM: udp
1.6 主动套接字,被动套接字
主动套接字:调用connect()函数发起连接
被动套接字:调用accept()函数接受连接
2. SOCKET函数解析
2.1 socket 函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol); //功能:创建一个套接字用于通信,
//相当于安装了一部电话机
功能:创建一个socket套接字
//domain: 指定通信协议族,AF_INET,PF_INET 这两者是一样的
//type: SOCK_DGRAM, SOCK_STREAM
//protocol:协议类型,习惯填0,或者显式指定协议类型IPPROTO_TCP
//返回值:成功返回非负整数,失败返回-1
2.2 bind 绑定函数
int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定一个本地地址到套接字
//sockfd: socket函数返回的套接字
//addr: 要绑定的地址
//addrlen: 地址长度
//返回值:成功返回0,失败返回-1
2.3 listen 监听函数
int listen(int sockfd, int backlog)
功能:将套接字用于监听进入的连接,将套接字由close转换成listen状态,转换成监听状态之后才能连接
//sockfd: socket函数返回的套接字
//backlog:规定内核为此套接字排队的最大连接个数,能够并发连接的数目,其值等于 已完成连接队列数+未完成队列数(三次握手未成功) 之和
//返回值:成功返回0,失败返回-1
2.4 accept 函数
int accpet(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:从已完成连接队列中返回第一个连接,如果已完成连接队列为空(说明没有三次握手成功的套接字),则阻塞
//sockfd: 服务器套接字
//addr: 将返回对等方的套接字地址
//addrlen: 返回对等方的套接字地址长度
//返回值:成功返回非负整数,失败返回-1
//相当于将对方来电号码填充进来,accpet函数会返回一个已连接套接字
2.5 connect 函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
功能:建立一个连接至addr所指定的套接字
//sockfd: 未连接套接字
//addr: 要连接的套接字地址
//addrlen: 第二个参数addr长度
//返回值:成功返回0,失败返回-1