Linux Socket 编程简介和实现

2019-10-10 14:34:49于丽

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;
  char buf[MAXLINE];
  int sockfd, n;
  char *str;
  
  if (argc != 2)
  {
    fputs("usage: ./client messagen", stderr);
    exit(1);
  }
  str = argv[1];
  
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);
  
  // 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
  // 注意,客户端不是不允许调用 bind(),只是没有必要调用 bind() 固定一个端口号,
  // 服务器也不是必须调用 bind(),但如果服务器不调用 bind(),内核会自动给服务器分配监听端口,
  // 每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
  connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

  write(sockfd, str, strlen(str));

  n = read(sockfd, buf, MAXLINE);
  printf("Response from server:n");
  write(STDOUT_FILENO, buf, n);
  printf("n");
  close(sockfd);
  return 0;
}

把上面的代码保存到文件 client.c 文件中,并执行下面的命令编译:

$ gcc client.c -o client

然后运行编译出来的 client 程序:

$ ./client hello

此时服务器端会收到请求并返回转换为大写的字符串,并输出相应的信息:

而客户端在发送请求后会收到转换过的字符串:

在客户端的代码中有两点需要注意:

1. 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
2. 客户端需要调用 connect() 连接服务器,connect 和 bind 的参数形式一致,区别在于 bind 的参数是自己的地址,而 connect 的参数是对方的地址。

至此我们已经使用 socket 技术完成了一个最简单的客户端服务器程序,虽然离实际应用还非常遥远,但就学习而言已经足够了。

提升服务器端的响应能力

虽然我们的服务器程序可以响应客户端的请求,但是这样的效率太低了。一般情况下服务器程序需要能够同时处理多个客户端的请求。可以通过 fork 系统调用创建子进程来处理每个请求,下面是大体的实现思路:

listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, ...);
while (1)
{
  connfd = accept(listenfd, ...);
  n = fork();
  if (n == -1)
  {
    perror("call to fork");
    exit(1);
  }
  else if (n == 0)
  {
    // 在子进程中处理客户端的请求。
    close(listenfd);
    while (1)
    {
      read(connfd, ...);
      ...
      write(connfd, ...);
    }
    close(connfd);
    exit(0);
  }
  else
  {
    close(connfd);
  }  
}