Linux 网络编程
socket概念
运行在计算机中的两个程序通过socket建立起一个管道,数据在管道中传输,socket把复杂的TCP/IP协议族隐藏了起来,对程序员来说,只要用好socket相关函数,就可以完成网络通信。
socket分类
socket提供了流(stream)和数据报(datagram)两种通信机制,即流socket和数据报socket
- 流socket基于TCP协议,传输数据不会丢失、重复、顺序错乱,可靠且双向
- 数据报socket基于UDP协议,不需要建立和维持链接,可能会丢失和错乱。效率高
相关的结构
socket函数
1
int socket(int domain, int type, int protocol);
参数:
- domain: 协议族。常用的协议族有AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE。在通信中必须采用对应的协议族,AF_INET决定要用ipv4地址(32bit)与端口号(16bit)的组合。
- type: socket类型。常用的类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等。SOCK_STREAM是面向连接的socket,针对于TCP服务;SOCK_DGRAM是无连接的socket,针对于UDP服务
- protocol: 指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
返回值:
- 成功返回一个int型的socket句柄,失败返回-1,错误原因存于 errno 中
注意:
- 一个程序最多可以打开1024个socket(因为linux系统限制一个文件可以打开文件数为1024,ulimit -a命令可以查到)
字节序转换函数
网络字节序由TCP/IP规定,采用大端存储格式;主机字节序跟CPU有关,有大端和小端。所以为了通信需要将主机字节序转为网络字节序。- htons(): 16位无符号 主机转网络
- ntohs(): 16位无符号 网络转主机
- htonl(): 32位无符号 主机转网络
- ntohl(): 32位无符号 网络转主机
sockaddr结构体
1
2
3
4
5struct sockaddr
{
unsigned short sa_family; //地址类型,AF_XXX
char sa_data[14]; //14字节=端口+地址+冗余位
}sa_data不好操作,所以有了改进版本
1
2
3
4
5
6
7
8
9
10
11
12struct sockaddr_in
{
unsigned short sa_family; //地址类型,AF_XXX
unsigned short int sin_port; //2字节端口号
struct in_addr sin_addr; //4字节地址号
unsigned char sin_zero[8]; //为了兼容sockaddr留的
}
struct in_addr
{
unsigned long s_addr; //4字节地址号
}bind函数
1
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
- sockfd: socket句柄
- addr: 存放通信端口和地址的结构体
- addrlen: addr结构体长度
返回值:
- 成功返回0,失败返回-1,错误原因存于 errno 中
注意:
- 第二个参数为旧版本sockaddr,使用新版本sockaddr_in的话强转一下就好(struct sockaddr *)sockaddr_in
地址与网络字节序地址互相转换函数
inet_aton函数
1
int inet_aton(const char *cp, struct in_addr *inp);
参数:
- cp: 字符串型IP地址
- inp: 网络字节序地址
返回值:
- 成功返回非0,地址不正确返回0
inet_ntoa函数
1
char *inet_ntoa(struct in_addr *in);
参数:
- in: 网络字节序地址
返回值:
- 字符串型IP地址
gethostbyname函数
1
struct hostent* gethostbyname(const char *name);
参数:
- name: 可以是IP地址,可以是域名,可以是主机名
返回值:
- hostent: 网络字节序地址
hostent结构体
1
2
3
4
5
6
7
8
9
10struct hostent
{
char *h_name; //主机名
char **h_aliases; //主机所有别名
int h_addrtype; //主机ip地址
int h_length; //主机ip地址长度
char **h_addr_list; //主机网络字节序型ip地址
}listen函数
1
int listen(int sockfd, int backlog);
服务器端对端口开始进行监听,如果有客户端通过connect发起请求,内核将会通过三次握手建立连接,然后将建立好的连接放入:已完成连接队列
sockfd: 服务端socket
backlog: 已完成连接队列最大个数-1
成功返回0,失败返回非0
connect函数
1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 客户端socket
addr: 要连接的服务端信息
addrlen: addr长度
成功返回0,失败返回非0
连接成功或者超时前会阻塞
accept函数
1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
从已完成连接队列中取出一个socket
sockfd: 服务端的listen socket
addr: 取出的客户端信息
addrlen: addr长度
成功返回一个socket句柄,可用于与该客户端的通信
如果队列为空,则会一直阻塞
send函数
1
ssize_t send(int sockfd, const void *buf, size_t len, flags)
发信息函数
sockfd: socket句柄
buf: buffer
len: buf长度
flags: 填0,其他值意义不大
成功返回发送字节数,出错返回-1,错误信息在errno
注意:网络断开或者socket对端关闭,send不会立即报错,要过几秒
recv函数
1
ssize_t recv(int sockfd, const void *buf, size_t len, flags)
收信息函数
sockfd: socket句柄
buf: buffer
len: buf最大长度
flags: 填0,其他值意义不大
成功返回已接受字节数,出错返回-1,对端关闭返回0,对端无发送则阻塞等待