首先我们先来看一下用到的socket API,这些函数都在sys/socket.h中。
socket():
int socket(int domain,int type,int protocol);
- socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
- 应用程序可以像读写文件一样用read/write在网络上收发数据;
- 如果socket()调用出错则返回-1;
- 对于IPV4,family参数指定为AF_INET;
- 对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议;
- protocol参数的介绍从略,指定为0即可。
bind():
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
- 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,服务器需要调用bind绑定的一个固定的网络地址和端口号;
- bind()成功返回0,失败返回-1;
- bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;
- struct sockaddr* 是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen制定结构体的长度。
listen():
int listen(int sockfd,int backlog);
- listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略,这里设置不会太大(一般是5);
- listen()成功返回0;失败返回-1。
accept():
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
- 三次握手完成后,服务器用accept()接受连接;
- 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户连接上来;
- addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
- 如果给addr参数传NULL,表示不关心客户端的地址;
- addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的,缓冲区addr的长度以避免缓冲区溢出的问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
server.c
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
void ProcessConnect(int new_sock) {
while (1) {
// 1. 尝试从客户端读取数据
char buf[1024] = {0};
// read/write recv/send
ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
continue;
}
if (read_size == 0) {
// 读到 EOF, 对于 TCP socket 来说, 表示对端主动关闭了链接
printf("[client %d] disconnect!\n", new_sock);
close(new_sock);
return;
}
buf[read_size] = '\0';
printf("[client %d] say %s\n", new_sock, buf);
// 2. 根据读取到的数据进行计算(由于我们是 echo_server, 此处
// 的计算就没有了)
// 3. 把计算生成的响应写回给客户端
write(new_sock, buf, strlen(buf));
}
}
// ./server [ip] [port]
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./server [ip] [port]\n");
return 1;
}
// 1. 创建文件描述符
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("socket");
return 1;
}
// 2. 绑定端口号
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return 1;
}
// 3. 监听端口号
ret = listen(listen_sock, 5);
if (ret < 0) {
perror("listen");
return 1;
}
// 到此处, 服务器初始化动作就完成了.
// 接下来就可以让客户端来链接了
printf("Server Start OK\n");
while (1) {
// 4. 循环的处理连接, 通过 accept 把
// 内核中已经建立好的连接拿到用户空间代码中进行处理
sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);
if (new_sock < 0) {
perror("accept");
continue;
}
printf("[client %d] connect\n", new_sock);
// 5. 使用 new_sock 完成数据的读写
ProcessConnect(new_sock);
}
close(listen_sock);
return 0;
}
client.c
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./client [ip] [port]\n");
return 1;
}
// 1. 创建 socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
// 2. 让客户端和服务器建立链接
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
int ret = connect(sock, (sockaddr*)&server_addr,
sizeof(server_addr));
if (ret < 0) {
perror("connect");
return 1;
}
while (1) {
// 3. 循环的尝试从标准输入读数据
printf("> ");
fflush(stdout);
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
return 1;
}
if (read_size == 0) {
printf("read done\n");
return 0;
}
buf[read_size] = '\0';
// 4. 把数据发送给服务器
write(sock, buf, strlen(buf));
// 5. 尝试从服务器读取数据
char buf_output[1024] = {0};
read_size = read(sock, buf_output, sizeof(buf_output) - 1);
if (read_size < 0) {
perror("read");
return 1;
}
if (read_size == 0) {
printf("read done\n");
return 0;
}
buf_output[read_size] = '\0';
// 6. 把读取到的结果写到标准输出上
printf("server resp %s\n", buf_output);
}
close(sock);
return 0;
}
server_fork.c
//server.c 只能连一个客户端,serverfork可以连接多个客户端
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
int ServerInit(char* ip, short port) {
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("socket");
return -1;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return -1;
}
ret = listen(listen_sock, 5);
if (ret < 0) {
perror("listen");
return -1;
}
return listen_sock;
}
void ProcessConnect(int new_sock) {
// 1. 创建子进程
pid_t ret = fork();
if (ret < 0) {
perror("fork");
return;
}
if (ret == 0) {
// 2. 子进程进行循环读写 socket 数据
while (1) {
char buf[1024] = {0};
ssize_t read_size = read(new_sock, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
exit(1);
}
if (read_size == 0) {
printf("[client %d] disconnected!\n", new_sock);
close(new_sock);
exit(0);
}
buf[read_size] = '\0';
printf("[client %d] say %s\n", new_sock, buf);
// 把数据写回给客户端
write(new_sock, buf, strlen(buf));
}
exit(0);
}
// 3. 父进程能够快速再次调用到 accept
// a) 关闭 new_sock
close(new_sock);
// b) 回收子进程, 使用忽略信号的方式来完成.
}
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./server [ip] [port]\n");
return 1;
}
signal(SIGCHLD, SIG_IGN);
// 1. 服务器初始化
int listen_sock = ServerInit(argv[1], atoi(argv[2]));
if (listen_sock < 0) {
printf("ServerInit failed\n");
return 1;
}
printf("ServerInit OK\n");
while (1) {
// 2. 循环进行 accept
sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);
if (new_sock < 0) {
perror("accept");
continue;
}
printf("[client %d] connect!\n", new_sock);
ProcessConnect(new_sock);
}
return 0;
}
Makefile
.PHONY:all
all:server client server_fork
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@
server_fork:server_fork.c
gcc $^ -o $@
.PHONY:clean
clean:
rm server client server_fork