0


linux篇【12】:计算机网络<中序>——tcp

查看TCP网络服务器情况和端口使用情况

  1. [zsh@ecs-78471 vscode]$ netstat -nltp
  2. (Not all processes could be identified, non-owned process info
  3. will not be shown, you would have to be root to see it all.)
  4. Active Internet connections (only servers)
  5. Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
  6. tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
  7. tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN -
  8. tcp6 0 0 :::22 :::* LISTEN -
  9. tcp6 0 0 ::1:25 :::* LISTEN -
  10. [zsh@ecs-78471 vscode]$

一.TCP套接字接口

1.inet_aton (和inet_addr一样,换一种方式而已)

int inet_aton(const char *cp, struct in_addr *inp);(address to net 本地字符串风格IP转网络4字节IP)cp:字符串风格IP地址。inp:转换后的存到inp中。

返回值:成功返回1;失败返回0;

注意:这类函数在转变IP风格时都会自动进行主机字节序和网络字节序之间的转换。

例:

  1. struct sockaddr_in local; // 用户栈
  2. memset(&local, 0, sizeof local);
  3. local.sin_family = PF_INET;
  4. local.sin_port = htons(port_);
  5. ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (
  6. inet_aton(ip_.c_str(), &local.sin_addr));

2.listen——把套接字设置为监听状态

监听socket,为何要监听呢?——因为tcp是面向连接的!(面向 的解释:做任何工作前先做什么就是面向什么,比如面向对象就是进行任何工作前先定义对象)

面向连接:就是进行任何工作前先建立连接,让服务器在任何时候可以被客户端去连接。为了面向连接需要把套接字设置为监听状态。

将socket套接字设置为监听状态,使得套接字在任何时候都可以随时被客户端连接

  1. man 3 listen

int listen(int socket, int backlog);

socket:要设置的文件描述符。backlog:后面再说,现在随便写,比如5。

返回值:成功返回0,失败返回-1 错误码被设置。

3.服务器获取客户端的连接 accept

  1. man 2 accept

** int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);

服务器通过特定套接字sockfd获取连接

sockfd:文件描述符。src_addr和addrlen这俩参数和recvfrom后面俩参数一模一样(客户端套接字):

addr:(输出型参数)当服务器读取客户端发送的消息时——哪个客户端给你发的消息,就把这个客户端套接字信息存入addr中。(addr的类型是套接字类型指针struct sockaddr,传入的网络套接字类型struct sockaddr_in需要强转成此类型指针 struct sockaddr*。)

addrlen:(输入输出型参数)客户端这个缓冲区大小。(socklen_t就是unsigned int)

返回值:成功返回一个新的socketfd,错误就返回-1错误码被设置。

返回值中套接字和参数中套接字的作用:

**int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); **

小故事:景点餐厅会雇一个专门迎客的人(张三),当张三把客人引进餐厅,就由服务员服务客人了。张三就走了,接着去迎客。*张三就是***int sockfd *参数中套接字-监听套接字 listensocke_,服务员就是*accept**返回值的套接字 serviceSock

监听套接字 listensocke_:参数中套接字的核心工作:获取新的连接(当然,一定是有客户来连接了! )

serviceSock返回值中套接字的核心工作:主要是为用户提供网络服务的socket,主要是为客户提供IO服务

4.用到的部分函数

(1)strcasecmp —— 比较两个字符串,但会忽略大小写

比较两个字符串,但会忽略大小写

如果发现s1(或其前n个字节)小于s2,则返回一个小于0的整数;s1等于s2,则返回0;s1大于s2,则返回大于0的整数。

(2)int isalpha(int c); ——是否是字母,是就返回1,不是就返回0。

(3)int islower(int c);——是否是小写字母,是就返回1,不是就返回0。

(4)int toupper(int c);——转换为大写字母。返回值是转换后的字母的值,如果无法进行转换,则为c。

(5)strcasecmp

比较了两个字符串s1和s2,忽略了字符的大小写。如果发现s1分别小于、匹配或大于s2,则它返回一个小于、等于或大于0的整数。

  1. if (strcasecmp(message.c_str(), "quit") == 0)
  2. quit = true;

返回值: 如果发现s1(或其前n个字节)小于、匹配或大于s2,则分别返回一个小于、等于或大小大于0的整数。

5.客户端发起连接 connect

connect 作用:通过客户端指定的流式套接字sockfd,向服务器发起链接请求(先填充需要连接的远端主机的基本信息)

*int connect(int sockfd, const struct sockaddr addr,socklen_t addrlen);

sockfd:流式套接字。src_addr和addrlen这俩参数和sendto后面俩参数一模一样

addr:(输入型参数)向哪个主机发消息,套接字类型指针struct sockaddr,传入的网络套接字类型struct sockaddr需要强转成此类型指针 struct sockaddr*。

addrlen:(输入型参数)主机这个缓冲区大小。(socklen t就是unsigned int)

返回值:发送连接成功返回0,连接失败就返回-1错误码被设置

(首次调用sendto 或 connect的时候 都会自动帮我们进行bind,client会自动bind自己的ip和port)

二.TCP套接字代码

1.注意事项

(1)因为TCP是流式类型套接字,所以利用read和write进行读取和写入数据。

(2)①客户端操作系统会自动bind,但是不需要自己显示的bind。

②需要listen吗?不需要的!监听本身就是等待别的客户端去连接你,所以客户端本身不需要设置监听状态,因为没人去连你的客户端

③需要accept吗?不需要的,都无法设置监听,就更不需要获取连接。

(3)TCP服务器的工作:①创建套接字。②bind绑定。③设置套接字监听状态。④服务器获取连接 accept。⑤获取客户端基本信息。⑥提供服务,读取内容后完成转换写回。

TCP客户端的工作:①创建套接字。② connect 填充服务器信息后向服务器发起链接请求。③写入数据后读出服务器转化的数据

(4)易错:服务器中

①accpet失败日志设置warnning,并continue重新获取客户端的连接

③memset(&local,0,sizeof(local));漏写(不写也行,但是写了给自己带来确定性,更好一些)

②inbuffer[s]='\0'; 漏写。

客户端中:

①volatile bool quit 漏写

② message.clear(); 和 message.resize(1024); 漏写。

2.封装服务为多进程,多线程版本

我们封装服务为多进程,多线程版本的目的:多个客户端访问时,让第一个客户端访服务器时,服务器上通过子进程为客户端提供服务,然后父进程就可以继续while循环,进行下一次阻塞式获取下一个客户端的链接并为他提供服务,是并发是进行的,

小提示:为什么不用waitpid()waitpid(); 默认是阻塞等待!我们本身就是追求多进程并发,阻塞相当于还是串行了,所以我们不能用waitpid()。那WNOHANG可以吗?——答:可以是可以,但是很麻烦,需要把各个子进程的pid保存进一个vector中,每次非阻塞等待需要轮询检测子进程pid看子进程是否退出,很麻烦,我们不选择这种方法。

(1)单进程(原始版本)

只能给一个客户提供服务,当为一个客户提供服务进入transService后,transService是死循环,除非提供完毕,否则函数不返回,则主执行流无法继续为其他客户提供服务

  1. void loop()
  2. {
  3. signal(SIGCHLD, SIG_IGN); // only Linux
  4. while (true)
  5. {
  6. struct sockaddr_in peer;
  7. socklen_t len = sizeof(peer);
  8. // 4. 获取连接, accept 的返回值是一个新的socket fd ??
  9. int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
  10. if (serviceSock < 0)
  11. {
  12. // 获取链接失败
  13. logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
  14. continue;
  15. }
  16. // 4.1 获取客户端基本信息
  17. uint16_t peerPort = ntohs(peer.sin_port);
  18. std::string peerIp = inet_ntoa(peer.sin_addr);
  19. logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
  20. strerror(errno), peerIp.c_str(), peerPort, serviceSock);
  21. // 5 提供服务, echo -> 小写 -> 大写
  22. // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
  23. transService(serviceSock, peerIp, peerPort);
  24. close(serviceSock); //这一步是一定要做的!
  25. }
  26. }

(2)多进程版本1

利用signal(SIGCHLD, SIG_IGN); 父进程调用signal/sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

注意:①父进程打开的文件会被子进程继承,所以子进程中本身用不到“接客”的listenSock_,所以建议关掉此文件描述符。close(listenSock_); //建议(类似管道关闭不需要的读写端一样)

②父进程accept创建的提供服务的文件描述符serviceSock就是让子进程继承使用的,那么子进程已经继承serviceSock后,父进程就用不到了,就需要关闭父进程对应的serviceSock。close(serviceSock); //这一步是一定要做的!(类似管道关闭不需要的读写端一样)

  1. void loop()
  2. {
  3. signal(SIGCHLD, SIG_IGN); // only Linux
  4. while (true)
  5. {
  6. struct sockaddr_in peer;
  7. socklen_t len = sizeof(peer);
  8. // 4. 获取连接, accept 的返回值是一个新的socket fd ??
  9. int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
  10. if (serviceSock < 0)
  11. {
  12. // 获取链接失败
  13. logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
  14. continue;
  15. }
  16. // 4.1 获取客户端基本信息
  17. uint16_t peerPort = ntohs(peer.sin_port);
  18. std::string peerIp = inet_ntoa(peer.sin_addr);
  19. logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
  20. strerror(errno), peerIp.c_str(), peerPort, serviceSock);
  21. // 5 提供服务, echo -> 小写 -> 大写
  22. // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
  23. // transService(serviceSock, peerIp, peerPort);
  24. // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会
  25. pid_t id = fork();
  26. assert(id != -1);
  27. if(id == 0)
  28. {
  29. close(listenSock_); //建议
  30. //子进程
  31. transService(serviceSock, peerIp, peerPort);
  32. exit(0); // 进入僵尸
  33. }
  34. // 父进程
  35. close(serviceSock); //这一步是一定要做的!
  36. }
  37. }

监控脚本:

(3)多进程版本2

通过创建孙子进程,孙子进程的爸爸直接终止,所以孙子进程是孤儿进程,孙子进程被系统领养,他的回收问题就交给了系统来回收。而爸爸进程通过爷爷进程来阻塞等待释放。

  1. void loop()
  2. {
  3. // signal(SIGCHLD, SIG_IGN); // only Linux
  4. while (true)
  5. {
  6. struct sockaddr_in peer;
  7. socklen_t len = sizeof(peer);
  8. // 4. 获取连接, accept 的返回值是一个新的socket fd ??
  9. int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
  10. if (serviceSock < 0)
  11. {
  12. // 获取链接失败
  13. logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
  14. continue;
  15. }
  16. // 4.1 获取客户端基本信息
  17. uint16_t peerPort = ntohs(peer.sin_port);
  18. std::string peerIp = inet_ntoa(peer.sin_addr);
  19. logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
  20. strerror(errno), peerIp.c_str(), peerPort, serviceSock);
  21. // 5.1 v1.1 版本 -- 多进程版本 -- 也是可以的
  22. //爷爷进程
  23. pid_t id = fork();
  24. if(id == 0)
  25. {
  26. // 爸爸进程
  27. close(listenSock_);//建议
  28. // 又进行了一次fork,让 爸爸进程直接终止
  29. if(fork() > 0) exit(0);
  30. // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
  31. transService(serviceSock, peerIp, peerPort);
  32. exit(0);
  33. }
  34. // 父进程
  35. close(serviceSock); //这一步是一定要做的!
  36. // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
  37. pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
  38. assert(ret > 0);
  39. (void)ret;
  40. }
  41. }

(4)多线程版本

利用多线程去服务客户,首先创造一个ThreadData类,方便函数方法调用transService传参。

  1. class ServerTcp; // 申明一下ServerTcp
  2. class ThreadData
  3. {
  4. public:
  5. uint16_t clientPort_;
  6. std::string clinetIp_;
  7. int sock_;
  8. ServerTcp *this_;
  9. public:
  10. ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
  11. : clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
  12. {}
  13. };
  14. ————————上面是类外,下面是类内
  15. static void *threadRoutine(void *args)
  16. {
  17. pthread_detach(pthread_self()); //设置线程分离
  18. ThreadData *td = static_cast<ThreadData*>(args);
  19. td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
  20. delete td;
  21. return nullptr;
  22. }
  23. void loop()
  24. {
  25. // signal(SIGCHLD, SIG_IGN); // only Linux
  26. while (true)
  27. {
  28. struct sockaddr_in peer;
  29. socklen_t len = sizeof(peer);
  30. // 4. 获取连接, accept 的返回值是一个新的socket fd ??
  31. int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
  32. if (serviceSock < 0)
  33. {
  34. // 获取链接失败
  35. logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
  36. continue;
  37. }
  38. // 4.1 获取客户端基本信息
  39. uint16_t peerPort = ntohs(peer.sin_port);
  40. std::string peerIp = inet_ntoa(peer.sin_addr);
  41. logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
  42. strerror(errno), peerIp.c_str(), peerPort, serviceSock);
  43. // 5 提供服务, echo -> 小写 -> 大写
  44. // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
  45. // 5.2 v2 版本 -- 多线程
  46. // 这里不需要进行关闭文件描述符吗??不需要啦
  47. // 多线程是会共享文件描述符表的!
  48. ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
  49. pthread_t tid;
  50. pthread_create(&tid, nullptr, threadRoutine, (void*)td);
  51. // logMessage(DEBUG, "server 提供 service start ...");
  52. // sleep(1);
  53. }
  54. }

2.代码

clientTcp.cc

  1. #include "util.hpp"
  2. // 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
  3. // 3. 需要listen吗?不需要的!
  4. // 4. 需要accept吗?不需要的!
  5. volatile bool quit = false;
  6. static void Usage(std::string proc)
  7. {
  8. std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
  9. std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
  10. << std::endl;
  11. }
  12. // ./clientTcp serverIp serverPort
  13. int main(int argc, char *argv[])
  14. {
  15. if (argc != 3)
  16. {
  17. Usage(argv[0]);
  18. exit(USAGE_ERR);
  19. }
  20. std::string serverIp = argv[1];
  21. uint16_t serverPort = atoi(argv[2]);
  22. // 1. 创建socket SOCK_STREAM
  23. int sock = socket(AF_INET, SOCK_STREAM, 0);
  24. if (sock < 0)
  25. {
  26. std::cerr << "socket: " << strerror(errno) << std::endl;
  27. exit(SOCKET_ERR);
  28. }
  29. // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
  30. // 2.1 先填充需要连接的远端主机的基本信息
  31. struct sockaddr_in server;
  32. memset(&server, 0, sizeof(server));
  33. server.sin_family = AF_INET;
  34. server.sin_port = htons(serverPort);
  35. inet_aton(serverIp.c_str(), &server.sin_addr);
  36. // 2.2 发起请求,connect 会自动帮我们进行bind!
  37. if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
  38. {
  39. std::cerr << "connect: " << strerror(errno) << std::endl;
  40. exit(CONN_ERR);
  41. }
  42. std::cout << "info : connect success: " << sock << std::endl;
  43. std::string message;
  44. while (!quit)
  45. {
  46. message.clear();
  47. std::cout << "请输入你的消息>>> ";
  48. std::getline(std::cin, message);
  49. if (strcasecmp(message.c_str(), "quit") == 0)
  50. quit = true;
  51. ssize_t s = write(sock, message.c_str(), message.size());
  52. if (s > 0)
  53. {
  54. message.resize(1024);
  55. ssize_t s = read(sock, (char *)(message.c_str()), 1024);
  56. if (s > 0)
  57. message[s] = 0;
  58. std::cout << "Server Echo>>> " << message << std::endl;
  59. }
  60. else if (s <= 0)
  61. {
  62. break;
  63. }
  64. }
  65. close(sock);
  66. return 0;
  67. }

serverTcp.cc

  1. #include "util.hpp"
  2. #include <signal.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <pthread.h>
  6. class ServerTcp; // 申明一下ServerTcp
  7. class ThreadData
  8. {
  9. public:
  10. uint16_t clientPort_;
  11. std::string clinetIp_;
  12. int sock_;
  13. ServerTcp *this_;
  14. public:
  15. ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
  16. : clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
  17. {}
  18. };
  19. class ServerTcp
  20. {
  21. public:
  22. ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
  23. {
  24. }
  25. ~ServerTcp()
  26. {
  27. }
  28. public:
  29. void init()
  30. {
  31. // 1. 创建socket
  32. listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
  33. if (listenSock_ < 0)
  34. {
  35. logMessage(FATAL, "socket: %s", strerror(errno));
  36. exit(SOCKET_ERR);
  37. }
  38. logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);
  39. // 2. bind绑定
  40. // 2.1 填充服务器信息
  41. struct sockaddr_in local; // 用户栈
  42. memset(&local, 0, sizeof local);
  43. local.sin_family = PF_INET;
  44. local.sin_port = htons(port_);
  45. ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
  46. // 2.2 本地socket信息,写入sock_对应的内核区域
  47. if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
  48. {
  49. logMessage(FATAL, "bind: %s", strerror(errno));
  50. exit(BIND_ERR);
  51. }
  52. logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);
  53. // 3. 监听socket,为何要监听呢?tcp是面向连接的!
  54. if (listen(listenSock_, 5 /*后面再说*/) < 0)
  55. {
  56. logMessage(FATAL, "listen: %s", strerror(errno));
  57. exit(LISTEN_ERR);
  58. }
  59. logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
  60. // 运行别人来连接你了
  61. }
  62. static void *threadRoutine(void *args)
  63. {
  64. pthread_detach(pthread_self()); //设置线程分离
  65. ThreadData *td = static_cast<ThreadData*>(args);
  66. td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
  67. delete td;
  68. return nullptr;
  69. }
  70. void loop()
  71. {
  72. // signal(SIGCHLD, SIG_IGN); // only Linux
  73. while (true)
  74. {
  75. struct sockaddr_in peer;
  76. socklen_t len = sizeof(peer);
  77. // 4. 获取连接, accept 的返回值是一个新的socket fd ??
  78. int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
  79. if (serviceSock < 0)
  80. {
  81. // 获取链接失败
  82. logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
  83. continue;
  84. }
  85. // 4.1 获取客户端基本信息
  86. uint16_t peerPort = ntohs(peer.sin_port);
  87. std::string peerIp = inet_ntoa(peer.sin_addr);
  88. logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
  89. strerror(errno), peerIp.c_str(), peerPort, serviceSock);
  90. // 5 提供服务, echo -> 小写 -> 大写
  91. // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
  92. // transService(serviceSock, peerIp, peerPort);
  93. // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
  94. // pid_t id = fork();
  95. // assert(id != -1);
  96. // if(id == 0)
  97. // {
  98. // close(listenSock_); //建议
  99. // //子进程
  100. // transService(serviceSock, peerIp, peerPort);
  101. // exit(0); // 进入僵尸
  102. // }
  103. // // 父进程
  104. // close(serviceSock); //这一步是一定要做的!
  105. // 5.1 v1.1 版本 -- 多进程版本 -- 也是可以的
  106. // 爷爷进程
  107. // pid_t id = fork();
  108. // if(id == 0)
  109. // {
  110. // // 爸爸进程
  111. // close(listenSock_);//建议
  112. // // 又进行了一次fork,让 爸爸进程
  113. // if(fork() > 0) exit(0);
  114. // // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
  115. // transService(serviceSock, peerIp, peerPort);
  116. // exit(0);
  117. // }
  118. // // 父进程
  119. // close(serviceSock); //这一步是一定要做的!
  120. // // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
  121. // pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
  122. // assert(ret > 0);
  123. // (void)ret;
  124. // 5.2 v2 版本 -- 多线程
  125. // 这里不需要进行关闭文件描述符吗??不需要啦
  126. // 多线程是会共享文件描述符表的!
  127. ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
  128. pthread_t tid;
  129. pthread_create(&tid, nullptr, threadRoutine, (void*)td);
  130. // waitpid(); 默认是阻塞等待!WNOHANG
  131. // 方案1
  132. // logMessage(DEBUG, "server 提供 service start ...");
  133. // sleep(1);
  134. }
  135. }
  136. // 大小写转化服务
  137. // TCP && UDP: 支持全双工
  138. void transService(int sock, const std::string &clientIp, uint16_t clientPort)
  139. {
  140. assert(sock >= 0);
  141. assert(!clientIp.empty());
  142. assert(clientPort >= 1024);
  143. char inbuffer[BUFFER_SIZE];
  144. while (true)
  145. {
  146. ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为我们读到的都是字符串
  147. if (s > 0)
  148. {
  149. // read success
  150. inbuffer[s] = '\0';
  151. if(strcasecmp(inbuffer, "quit") == 0)
  152. {
  153. logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
  154. break;
  155. }
  156. logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
  157. // 可以进行大小写转化了
  158. for(int i = 0; i < s; i++)
  159. {
  160. if(isalpha(inbuffer[i]) && islower(inbuffer[i]))
  161. inbuffer[i] = toupper(inbuffer[i]);
  162. }
  163. logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
  164. write(sock, inbuffer, strlen(inbuffer));
  165. }
  166. else if (s == 0)
  167. {
  168. // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
  169. // s == 0: 代表对方关闭,client 退出
  170. logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
  171. break;
  172. }
  173. else
  174. {
  175. logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
  176. break;
  177. }
  178. }
  179. // 只要走到这里,一定是client退出了,服务到此结束
  180. close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
  181. logMessage(DEBUG, "server close %d done", sock);
  182. }
  183. private:
  184. // sock
  185. int listenSock_;
  186. // port
  187. uint16_t port_;
  188. // ip
  189. std::string ip_;
  190. };
  191. static void Usage(std::string proc)
  192. {
  193. std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
  194. std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;
  195. }
  196. // ./ServerTcp local_port local_ip
  197. int main(int argc, char *argv[])
  198. {
  199. if(argc != 2 && argc != 3 )
  200. {
  201. Usage(argv[0]);
  202. exit(USAGE_ERR);
  203. }
  204. uint16_t port = atoi(argv[1]);
  205. std::string ip;
  206. if(argc == 3) ip = argv[2];
  207. ServerTcp svr(port, ip);
  208. svr.init();
  209. svr.loop();
  210. return 0;
  211. }

util.hpp

  1. #pragma once
  2. #include <iostream>
  3. #include <string>
  4. #include <cstring>
  5. #include <cstdlib>
  6. #include <cassert>
  7. #include <ctype.h>
  8. #include <unistd.h>
  9. #include <strings.h>
  10. #include <sys/types.h>
  11. #include <sys/socket.h>
  12. #include <netinet/in.h>
  13. #include <arpa/inet.h>
  14. #include "log.hpp"
  15. #define SOCKET_ERR 1
  16. #define BIND_ERR 2
  17. #define LISTEN_ERR 3
  18. #define USAGE_ERR 4
  19. #define CONN_ERR 5
  20. #define BUFFER_SIZE 1024
标签: linux tcp/ip

本文转载自: https://blog.csdn.net/zhang_si_hang/article/details/128269579
版权归原作者 beyond.myself 所有, 如有侵权,请联系我们删除。

“linux篇【12】:计算机网络<中序>——tcp”的评论:

还没有评论