135-大写转换服务器

相信你已经基本掌握了最最最简单的基于 TCP 连接的服务器编程技术了。本文算打算完成一个小型服务器,该服务器接受客户端发送过来的数据,然后将数据中的字母转换成大写后再发送给客户端。

1. read 和 write

(1) read

在这里,我们需要将 read 函数再次出来讲讲。

以前我们遇到的 read 的返回值一般不是大于 0 就是小于 0. 大于 0 的返回值表示 read 到的数据字节数,小于 0 表示出错:

  • 返回值 > 0: 读取的字节数。
  • 返回值 < 0: 出错,同时设置 errno 变量。

如果数据读完了,你还继续 read,就会阻塞。

在网络通信程序中,如果通信链路对端关闭,此时 read 函数就会返回 0. 这个特性实际上我们之前学管道的时候也遇到过,如果一端关闭,则 read 返回 0. 实际上,返回 0 表示读到了文件的结尾,也就是说收到了 EOF 字符。而对端关闭后,操作系统会发送一个 EOF 字符过来。

(2) write

对于 write 函数来说,如果对端关闭了,你还继续写,write 返回 -1,errno 设置为 EPIPE,同时进程收到 SIGPIPE 信号。

2. 大写转换服务器编写思路

服务器程序实际上我们可以直接把之前的写的代码修改一下就行了,大体的框架就仍然是安装一个 socket,bind 套接字地址,转换成被动 socket,然后 accept 连接建立通信链路。

一旦链路建立,就开始我们的业务,即从 socket 中读取数据,转换成大写后再写回 socket.

按照 Linux 的一切皆文件的思想,我们把 socket 描述符当成文件描述符直接 read、write 就行了,相当方便,注意,socket 描述符是既可读也可写的,就好像是以 O_RDWR 打开的一样。

3. 程序清单

serv.c 程序接受链接,将客户端发来的数据转换成大写发送回去。cli.c 程序从标准输入读取数据,然后发送到服务器,接着 cli 程序等待处理结果,接收到结果后,将结果打印在屏幕上。

3.1 serv 服务器程序

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>


#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

// 将数据转换成大写,toupper 函数是 C 标准库提供的,可以直接使用。
void upper(char* buf) {
  char* p = buf;
  while(*p) {
    *p = toupper(*p);
    ++p;
  }
}

int main() {
  struct sockaddr_in servaddr, cliaddr;
  int sockfd, clientfd, ret, n;
  socklen_t cliaddrlen;
  char buf[64];

  // 1. create sockaddr
  puts("1. create sockaddr");
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(8080);

  // 2. create socket
  puts("2. create socket");
  sockfd = socket(AF_INET, SOCK_STREAM, 0); 
  if (sockfd < 0) ERR_EXIT("socket");

  // 3. bind sockaddr
  puts("3. bind sockaddr");
  ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
  if (ret < 0) ERR_EXIT("bind");

  // 4. listen
  puts("4. listen");
  ret = listen(sockfd, 5); 
  if (ret < 0) ERR_EXIT("listen");

  // 5. accept connect
  puts("5. accept connect");
  cliaddrlen = sizeof(cliaddr);
  clientfd = accept(sockfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
  if (clientfd < 0) ERR_EXIT("accept");
  printf("client fd: %d\n", clientfd);
  printf("sockaddr: %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

  // 6. getsockname, 打印新的 socket 绑定的套接字地址 
  puts("6. getsockname");
  cliaddrlen = sizeof(cliaddr);
  ret = getsockname(clientfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
  if (ret < 0) ERR_EXIT("getsockaddr");
  printf("sockaddr: %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

  // 7. send data
  while(1) {
    // 从客户端读取数据,如果返回 0 表示对端关闭
    n = read(clientfd, buf, 63);
    if (n == 0) {
      puts("peer closed");
      break;
    }
    buf[n] = 0;
    // 将缓冲区中的数据转换成大写。
    upper(buf);
    // 发送回客户端
    write(clientfd, buf, n);
  }
  close(clientfd);
  close(sockfd);

  return 0;
}

3.2 cli 客户端程序

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
  int sockfd, ret, n;
  char buf[64];
  struct sockaddr_in servaddr;
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;

  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  servaddr.sin_port = htons(8080);

  sockfd = socket(AF_INET, SOCK_STREAM, 0); 
  if (sockfd < 0) ERR_EXIT("socket");

  ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
  if (ret < 0) ERR_EXIT("connect");

  cliaddrlen = sizeof(cliaddr);
  ret = getsockname(sockfd, (struct sockaddr*)&cliaddr, &cliaddrlen);
  if (ret < 0) ERR_EXIT("getsockaddr");

  printf("cliaddr: %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

  while(1) {
    // 从标准输入读取数据
    scanf("%s", buf);
    // 如果收到的第一个字母是 q 就退出循环,关闭连接。
    if (buf[0] == 'q') break;
    // 将数据发送给服务器
    write(sockfd, buf, strlen(buf));
    // 从服务器读取处理完的数据
    n = read(sockfd, buf, 63);
    buf[n] = 0;
    // 将结果打印在屏幕上
    puts(buf);
  }

  close(sockfd);
  return 0;
}

3.3 编译和运行

$ gcc serv.c -o serv
$ gcc cli.c -o cli


这里写图片描述
图1 运行结果

可以看到我们的程序可以正常运行啦,最后在客户端输入 q 后,客户端关闭了连接,服务器收到 EOF 后,read 返回了 0,就打印 peer closed(对端关闭),然后退出。

4. 总结

  • 掌握基本 TCP 编程步骤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值