TCP Server-Client 实现

出于练习目的,决定自己实现一遍client-server的基础模型. 源码

Server端

1.申明并取得启用服务地址的一系列参数(这里通过命令行输入).

1
2
3
4
5
6
7
8
9
10
11
struct sockaddr_in server;
struct sockaddr_in client;
int port, connectfd, sin_size;
sin_size = sizeof(struct sockaddr_in);

port = htons(atoi(argv[2])); //端叙转换,atoi只用于转换数字
//转化时跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('/0')才结束转换,并将结果返回。

server.sin_family = AF_INET;
server.sin_port = port;
server.sin_addr.s_addr = htonl(INADDR_ANY); //localhost

2.创建socket, 并设置为resuable(并发服务器)

1
2
3
int socketfd = socket(AF_INET, SOCK_STREAM, 0); //返回int
int opt = SO_REUSEADDR;
setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

3.bind 和listen

1
2
3
4
5
6
7
8
9
if(bind(socketfd, (struct sockaddr *) &server, sizeof(struct sockaddr)) == -1) {
  perror("Bind error");
  exit(1);
}

if(listen(socketfd, 5) == -1) {
  perror("listen() error\n");
  exit(1);
}

4.accept卡住程序,等待客户连入,一但连入,开thread处理client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if((connectfd = accept(socketfd, (struct sockaddr *)&client, (socklen_t *)&sin_size)) == -1){ //connectfd is only for process request
  perror ("accept() error \n");
  exit(1);

  struct ARG* arg = (struct ARG*)malloc(sizeof(struct ARG));
  arg->connfd = connectfd;
  connarr[cnt++] = connectfd;
  printf("connect fd after is : %d", arg->connfd);
  arg->client = client;
  if(pthread_create(&thread, NULL, start_routine, (void*)arg)) { //if get strange thing, most likely is pass parameter
      perror("pthread create error");
      exit(1);
  }
}

Client端

1.载入server参数

1
2
3
4
5
6
7
8
9
struct sockaddr_in server_addr;
//两种方法获取server_addr
//1. 可以读取localhost
struct hostent *server;
server = gethostbyname(argv[1]);
bcopy(server->h_addr, &server_addr.sin_addr,server->h_length);

//2. 直接对server_addr放入ipaddr
socketaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 127.0.0.1可以是任何string

2.connect to the Server

1
2
3
if(connect(sockfd, (const struct sockaddr *)&server_addr,sizeof(server_addr)) < 0) {
  printf("fail to connect");
}

3.开thread去处理服务器发来的信息

1
2
3
4
if(pthread_create(&thread1, NULL, &start_receive, (void*)arg)){
  perror("listen create error");
  exit(1);
}

4.在另外的while loop里进行发送

1
2
3
4
while(1) { // must
  sendRequest(sockfd, argv[3]);
  //在sendRequest里用fegets卡住程序.
}

I/O复用提高效率

基本概念(从非阻塞轮询到select/poll到epoll)

1.非阻塞轮询:不停检测所有io,有数据则读取

1
2
3
4
5
6
7
while(true) {
  for (i in stream[]){
      if (i has data){
          read all;
      }
  }
}

2.为了避免cpu空转:引入select代理 同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流

1
2
3
4
5
6
7
8
while(true) {
  select(stream[]);
  for (i in stream[]){
      if (i has data){
          read all;
      }
  }
}

于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。

3.epoll (event poll) epoll会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的,复杂度降低到了O(k),k为产生I/O事件的流的个数.

1
2
3
4
5
6
while(true) {
  active_stream[] = epoll_wait(epollfd)
      for (i in active_stream[]) {
          read or write till unavailable
      }
}