socket编程
基本函数和结构体
创建socket
int socket(int domain, int type, int protocol)
成功返回0
;失败返回-1
,同时设置错误代码errno
。 单个进程能够创建socket
连接的数量受系统参数open files
的限制(使用ulimit -a
查看)。因为socket
在Linux
中也是文件domain
:通信协议族PF_INET
:ipv4
协议族PF_INET6
:ipv6
协议族PF_LOCAL
:本地通信的协议族PF_PACKET
:内核底层的协议族PF_IPX
:IPX Novel
协议族- 其余协议族不常用
type
:数据传输的类型SOCK_STREAM
:面向连接的socket
,- 数据不会丢失
- 数据顺序不会错乱
- 双向通道
SOCK_DGRAM
:无连接的socket
,- 传输效率更高
- 数据可能丢失
- 数据顺序可能错乱
protocal
:最终使用的协议- 在
ipv4
协议族中,数据传输方式为SOCK_STREAM
的协议只有IPPROTO_TCP
,数据传输方式为SOCK_DGRAM
的协议只有IPPROTO_UDP
. - 该参数也可以为0
- 在
TCP
和UDP
.[[TCP协议和UDP协议]]主机字节序和网络字节序
- 主机字节序:分为大端序和小端序。
每个地址内放
1Byte
(8bit
),大端序和小端序讨论多个字节(Byte
)的地址高低问题- 大端序:低高高低.
==低位==
Byte
存放在==高位==,==高位==Byte
存放在==低位==. - 小端序:低低高高.
==低位==
Byte
存放在==低位==,==高位==Byte
存放在==高位==.总结:大端序按照原来顺序存储,小端序按字节颠倒顺序存储。
- 造成的问题:同样的数据,大端序的计算机和小端序的计算机解析方式不同,那么得到的内容也不一样。在网络传输中,容易出现问题。
- 大端序:低高高低.
==低位==
- 网络字节序(大端序):解决不同字节序的计算机之间传输数据的问题
C语言提供了四个函数用于网络字节序和主机字节序的转换:
uint16_t htons(uint16_t hostshort)
:将16位的整数从主机字节序转换为网络字节序。uint32_t htonl(uint32_t hostlong)
:将32位的整数从主机字节序转换为网络字节序。uint16_t stohs(uint16_t netshort)
:将16位的整数从网络字节序转换为主机在字节序。uint32_t stohl(uint32_t netlong)
:将32位的整数从网络字节序转换为主机字节序。h: host
主机n: net
网络s: short
2字节,16位的整数l: long
4字节,32位的整数
- 主机字节序:分为大端序和小端序。
每个地址内放
ip
地址和通讯端口port
.ipv4
地址用4字节(32bit
)的整数存放,port
用2字节(16bit
)的整数存放(0~65535)。 为什么不用字符串? 因为192.168.190.134
用字符串存储需要15字节,而用整数存储只需要4个字节:3232284294。(思考ipv4
地址最大只能到255.255.255.255
)万恶的结构体 因为平时用到的
ip
地址都是字符串类型的,而程序中存储的是int
类型那么就需要将字符串类型的ip转换为int型ip。主要涉及三个结构体和一个函数sockaddr
结构体 存储协议族、端口、地址信息。客户端的connetc
函数和服务端的bind
函数都要用到这个结构体。其定义类似下图:1
2
3
4struct sockaddr{
unsigned short int sa_family; // 协议族
unsigned char sa_data[14]; // 14字节的端口和地址
}sockaddr_in
结构体 上方的sockaddr
是为了统一地址结构的表示方法,统一接口函数。但是很难用,操作不方便。所以定义了等价的sockaddr_in
结构体,其大小与sockaddr
相同,可以强制转换。1
2
3
4
5
6
7
8
9struct sockaddr_in{
unsigned short int sa_family; // 协议族
unsigned short sin_port; // 16bit端口号
struct in_addr sin_addr; // 32bit的地址(只能存储ipv4的地址,不能存储ipv6。sockaddr中14个字节的char数组就是预留了ip地址扩展的空间)
unsigned char sin_zero[8];// 未使用,为了保持与sockaddr长度相同而添加。
}
struct in_addr{
unsigned int s_addr; // 32bit的ip地址,大端序
}gethostbyname()
函数 根据域名、主机名、字符串ip
地址获取大端存储的int32
类型ip
.1
struct hostent* gethostbyname(const char* name);
hostent
结构体1
2
3
4
5
6
7struct hostent{
char* h_name; // 主机名
char** h_aliases; // 主机所有别名构成的的字符串数组。(同一个ip可以绑定多个域名)
short h_addrtype; // 主机ip地址的类型,如ipv4(AF_INET)还是ipv6
short h_length; // 主机ip地址长度,ipv4为4(字节),ipv6为16(字节)
char** h_addr_list; // 主机的ip地址,以网络字节序存储
}获取
hostent
后,使用以下代码把大端序的地址复制到sockaddr_in
结构体的sin_addr
成员中1
2
3sockaddr_in serveraddr;
struct hostent* h = gethostbyname("192.168.1.2");
memcpy(&serveraddr.sin_addr, h->h_addr_list, h->h_length);
字符串
ip
转大端序ip
. C语言提供了几个库函数,用于字符串ip
和大端序ip
的相互转换。通常用于网络通讯的服务端程序中。1
2
3
4
5
6
7
8
9
10typedef unsigned int uint32;
// 把字符串格式的ip转换成大端序的ip,转换后的ip应该赋值给 sockaddr_in.in_addr.s_addr。不能传入域名
uint_32 inet_addr(const char* cp);
// 将字符串格式的ip转换为大端序的ip,转换后的ip直接填充到sockaddr_in.in_addr成员(不需要手动填充)。不能传入域名
int inet_aton(const char* cp, struct int_addr * inp);
// 将大端序ip转换为字符串格式的ip。用于在服务器程序中解析客户端的ip地址
char* inet_ntoa(struct in_addr in);bind
函数 绑定服务端的ip
和端口,失败返回-11
2struct sockaddr server_addr;
bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr))lieten
函数 把socket设置为可连接(监听)的状态,失败返回-1listen(listenfd, 5)
封装socket客户端
1 |
|
封装socket服务端
单进程单线程服务端
1 |
|
多进程服务端
1 |
|
文件传输
客户端
1 |
|
服务端
1 |
|