导航
当前章节:网络通信socket
原文链接:网络通信socket
上一章节:进程间通信IPC
目录:Linux程序设计
socket
介绍
套接字是网络通信的端点。它允许进程通过网络进行通信,交换字节流。
服务器端创建socket,通过bind转为被动,再通过accept接收连接的客户端
客户端建立socket后直接connect与服务器连接,连接成功后可以通过socket进行网络通信。
socket创建
int socket(int domain, int type, int protocol);
参数:
domain 地址族,IP 地址类型AF_INET/AF_INET6 表示ipv4和ipv6
type:SOCK_STREAM/SOCK_DGRAM 为字节流和数据包传输方式
protocol 默认0自动选择协议,IPPROTO_TCP流式TCP, IPPTOTO_UDP数据报UDP
返回
成功返回对应socketid,否则-1
addr数据结构
在进行连接过程中,需要指定连接服务器的ip和端口。
结构sockaddr是一个通用的套接字地址结构。它保存各种地址族的地址信息,并作为参数传递给bind()、connect()和sendto()等套接字函数。
<sys/socket.h>:
struct sockaddr {
unsigned short sa_family; // address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
struct sockaddr_in {
short sin_family; // Address family, AF_INET
unsigned short sin_port; // Port number
struct in_addr sin_addr; // IPv4 address
char sin_zero[8]; // Padding
};
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8000);
inet_pton(AF_INET, “127.0.0.1”, &addr.sin_addr);
字节序转换
计算机存储有大端存储和小端存储,大端存储低地址放数据的高位,小端存储相反,网络字节序指定大端存储,因此需要转换。
int inet_pton(int af, const char *src, void *dst); 将字符串ip转为网络字节序
inet_ntop(int af, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
INET_ADDRSTRLEN是一个宏,定义该缓冲区的最大大小(IPv4为16字节,IPv6为46字节)。
addr.sin_addr.s_addr = htonl(INADDR_ANY);
uint16_t htons(uint16_t hostshort); 将主机字节序的端口转为网络字节序。
ntohs(client_addr.sin_port); 将网络字节序的端口转为主机字节序。
bind绑定
int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen);
bind(sockfd, (struct sockaddr) &addr, sizeof(addr));
成功返回0 错误返回-1
- 它用于将网络地址和端口号与套接字相关联。
- 在流套接字可以接受传入连接或数据报套接字可以接收数据包之前,必须调用它。
- addr指向指定地址和端口的sockaddr结构。INADDR_ANY可用于侦听所有接口。
- 如果不同的地址族使用不同大小的sockaddr结构,则必须指定addrlen。
- 套接字一旦绑定,就无法绑定到另一个地址。必须再次调用bind()才能更改绑定。
- 在已连接或已绑定的套接字上调用bind()将失败,errno将设置为EINVAL。
- 成功后,bind()返回0。出现错误时,将返回-1,并适当设置errno。
- 1024以下的端口号是为系统服务保留的,除非程序具有根权限,否则无法绑定到。
- 指定已在使用的地址将导致“地址已在使用”错误(EADDRINUSE)。一个地址/端口对只能绑定一个套接字。
- Unix域套接字不需要调用bind(),它们可以直接使用connect()。但如果调用bind(),addr必须指向sockaddr_un结构。
- 一旦绑定,套接字即使在创建它的进程终止后仍然存在,直到设置了SO_REUSEADDR选项,并且另一个套接字绑定到同一地址/端口对。
bind()中的一些错误示例如下:
-EINVAL-套接字已绑定到一个地址。
-EADDRNOTAVAIL-指定的地址无法绑定。
-EADDRINUSE-指定的地址已在使用中。
-EACCESS-权限不足,无法绑定端口。
listen监听
将主动的socket转为被动,并设置最大监听数量
listen(listenfd, 10);
accept接受
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
传入server的socketfd并传入存放client地址的addr,以及长度。
返回与client连接的新的fd,为-1表示连接失败
connect连接
connect()系统调用将套接字连接到远程地址和端口。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客户端需要与服务器端口和ip一致,否则无法连接
连接成功返回0,连接传入的fd可以与服务器通信,否则-1
通信
send和recv直接通信
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
flag一般设置0
发送成功返回发送直接数,否则-1
接收成功返回接收字节数,0为对方关闭套接字,-1为接收失败read() / write()
直接使用文件读写方式,这种方式容易出现问题。sendmsg/recvmsg
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
它们的工作方式与send()和recv()类似,但允许在msghdr结构中传递额外的元数据和控制信息。
struct msghdr {
void *msg_name; // protocol address
socklen_t msg_namelen; // size of protocol address
struct iovec *msg_iov; // scatter/gather array
int msg_iovlen; // elements in msg_iov
void *msg_control; // ancillary data (cmsghdr struct)
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
msghdr可以存放一个消息数组,消息数组里面可以有多个消息如下:
struct iovec {
void iov_base; / Starting address /
size_t iov_len; / Number of bytes to transfer */
};UDP数据通信
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);