6.7.1. socket概述
socket主要分为 流式套接字SOCK_STREAM 和 数据报套接字SOCK_DGRAM . SOCK_STREAM是一种可靠的,双向的有序的通讯流,对应使用的是TCP协议.SOCK_DGRAM是
不可靠的,无序的通讯流,对应使用的UDP协议
基本的编程接口有: socket, bind, listen, accept, connect, send, recv等
6.7.1.1. socket函数接口
创建通信套接字:socket
socket函数创建一个通信的端点,并返回一个指向该端点的文件描述符(linux下一切皆是文件)
#include <sys/types.h>
#include <sys/socket.h>
/*
 * domain: 通信协议簇,例如AF_INET, AF_UNIX...
 * type: SOCK_STREAM, SOCK_DGRAM
 * protocal: 通常为0
 * return: 成功返回文件描述符,失败返回-1,并设置erron
 */
int socket(int domain, int type, int protocol);
例如服务器端创建一个用于接收客户端连接的socket代码
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == server_fd) {
    perror("socket");
    exit(-1);
}
分配套接字名称: bind
当用socket函数创建套接字后,并没有为它分配IP地址和端口,我们还需要bind函数来将指定的IP和端口号分配给已经创建的socket
#include <sys/types.h>
#include <sys/socket.h>
/*
 * sockfd: socket 返回的文件描述符
 * addr: 含有要绑定的IP和端口的地址结构体指针
 * addrlen: 第二个参数的大小,使用sizeof来计算
 * return: 成功返回0, 失败返回-1,并设置erron
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
注解
第二个参数需要注意,参数指定的是struct sockaddr *类型,一般不直接使用这个结构体,这个类型在linux上有需要的变种,例如sockaddr_in和sockaddr_un, 经常使用后面这两个结构定义IP和端口,然后强制转换
// struct sockaddr_un myaddr;
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;
//接受任何IP的连接
myaddr.sin_add.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(8080);
if(bind(server_fd, (struct sockaddr*)&myaddr, sizeof(sockaddr_in)) == -1) {
    perror("bind");
    exit(-1);
}
开始监听: listen
使用listen来建立一个监听客户端连接的队列
#include <sys/types.h>
#include <sys/socket.h>
/*
 * sockfd: 监听的socket描述符
 * backlog: 建立的最大连接数
 * return: 成功返回0, 失败返回-1,并设置erron
*/
int listen(int sockfd, int backlog);
例如创建一个可以监听10个客户端连接请求的队列
if(listen(server_fd, 10) == -1) {
    perror("listen");
    exit(-1);
}
接收连接请求: accept
网络编程的核心异步就是建立客户端和服务器端的连接,使用accept来建立两者的连接
#include <sys/types.h>
#include <sys/socket.h>
/*
 * sockfd: 已经创建的本地正在监听的socket
 * addr: 保存连接的客户端的地址信息
 * addrlen: sockaddr的长度指针
 * return: 成功返回客户端的socket文件描述符,失败返回-1,并设置erron
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
后面两个参数需要定义,但是不需要初始化,在连接成功后客户端的socket信息会自动填入
struct sockaddr_in clientaddr;
int clientaddr_len = sizeof(clientaddr);
int client_fd = accept(server_fd, (struct sockaddr *)&clientaddr, &clientaddr_len);
if(client_fd == -1) {
    perror("accept");
    exit(-1);
}
发送数据: send, sendto
在建立连接后,当然要发送数据,既然socket也是文件,发送数据其实就是写文件,我们使用send函数来发送socket数据
#include <sys/types.h>
#include <sys/socket.h>
/*
 * sockfd: 接收数据的socket
 * buf: 要发送的上数据
 * len: 数据长度
 * flags: 当这个参数为0,该函数等价与write
 * return: 成功返回发送的字节数,失败返回-1,并设置erron
*/
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/* sendto 功能是将数据发送到指定的地址dest_addr, 其他参数基本相同 */
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                const struct sockaddr *dest_addr, socklen_t addrlen);
例如服务器在建立连接后发送一个字符串到客户端:
char msg[] = "Hello Client...";
send(client_fd, msg, strlen(msg), 0);
sendto(client_fd, msg, strlen(msg), 0, (struct sockaddr*)&dst_addr, sizeof(dest_addr));
接收数据:recv, recvfrom
既然有发送数据,必然有接收数据的函数,与send类似, recv的功能也跟 read几乎相同
#include <sys/types.h>
#include <sys/socket.h>
/*
 * sockfd: 接收的socket fd
 * buf: 接收缓冲区
 * len: 缓冲区长度
 * flags: 当这个参数为0, 该函数等价为readd
 * return: 成功返回接收的字节数,失败返回-1,并设置erron
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sszie_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addr);
例如接收服务器发送的字符串
char msg_buf[100] = {0x55};
recv(server_fd, msg_buf, 100, 0);
int srcaddr_len = sizeof(src_addr);
recvfrom(server_fd, msg_buf, 100, 0, (struct sockaddr*)&src_addr, &srcaddr_len);