012.成型版回声服务器

简介: 带错误处理的回声服务器的实现结合之前的博客我们已经知道了什么是网络编程以及如何通过调用socket函数来编写一个回声服务器,这个过程中我们可能遇到了很多的问题,但是我们都一点一点的克服了。这本身就是一种进步了。这个部分就是我们的简单的回声服务器的最后一篇博客了。在完成这篇博客之后可以进行下一个部分的学习(实现高并发http 服务器)。

带错误处理的回声服务器的实现

结合之前的博客我们已经知道了什么是网络编程以及如何通过调用socket函数来编写一个回声服务器,这个过程中我们可能遇到了很多的问题,但是我们都一点一点的克服了。这本身就是一种进步了。这个部分就是我们的简单的回声服务器的最后一篇博客了。在完成这篇博客之后可以进行下一个部分的学习(实现高并发http 服务器)。

server.cpp

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<ctype.h>
#include "error_handling.h"
#define SERVER_PORT 6666
#define MAXLINE 100
int main(void) {
  /*定义  server端套接字文件描述符:sfd
        client套接字文件描述符:cfd
        read函数读取到的字符数:n */
  int sfd, cfd, n;
  /* server端地址定义(包含IP、PORT、协议族)暂未指定:server_addr
     client端地址定义(包含IP、PORT、协议族)不需要再server.c定义,accept函数会自动填充*/
  struct sockaddr_in server_addr, client_addr;
  socklen_t  client_len;//为 accept函数第三个参数做准备
  char buf[MAXLINE];//接收client端发来的字符的缓冲区
  /*bzero:将server端置空,为了给后续的IP、PORT、协议族赋值所用
   后续操作是为了 bind函数绑定IP、PORT和协议族的固定操作。*/
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;//IPV4
  server_addr.sin_port = htons(SERVER_PORT);//转换为网络字节序
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  sfd = Socket(AF_INET, SOCK_STREAM, 0);  //调用 socket函数值后会返回一个文件描述符
  Bind(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); //绑定IP、PORT、协议族
  Listen(sfd, 21);
  /* accept函数放在 while 里面和外面的结果是不一样的,
      accept放在while里面代表客户端只能和服务器端通信一次
      accept放在while外面那么客户端就可以一直和服务器进行通信
  */
  client_len = sizeof(client_addr);
  cfd = Accept(sfd, (struct sockaddr*)&client_addr, &client_len);//accept调用和会给server端返回一个和client端连接好的socket。
  while (1) {
    n = Read(cfd, buf, MAXLINE);
    for (int i = 0; i < n; i++) {
      buf[i] = toupper(buf[i]);
    }
    Write(cfd, buf, n);
  }
  Close(cfd);
  return 0;
}

client.cpp

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<ctype.h>
#include "error_handling.h"
#define SERVER_PORT 6666
#define MAXLINE 100
int main(void) {
  int sfd, n;
  struct sockaddr_in server_addr;
  char buf[MAXLINE];
  bzero(&server_addr, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVER_PORT);
  inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
  sfd = Socket(AF_INET, SOCK_STREAM, 0);
  connect(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
  while (1) {
    fgets(buf, sizeof(buf), stdin);
    Write(sfd, buf, strlen(buf));
    n = read(sfd, buf, sizeof(buf));
    Write(STDOUT_FILENO, buf, n);
  }
  Close(sfd);
  return 0;
}

error_handling.cpp

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<errno.h>
#include<ctype.h>
#include "error_handling.h"
void perr_exit(const char* s)
{
  perror(s);  //输出打印信息。
  exit(-1);
}
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
  int n;
again:
  if ((n = accept(fd, sa, salenptr)) < 0) {
    if ((errno == ECONNABORTED) || (errno == EINTR))
      goto again;
    else
      perr_exit("accept error");
  }
  return n;
}
int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
  int n;
  if ((n = bind(fd, sa, salen)) < 0)
    perr_exit("bind error");
  return n;
}
int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
  int n;
  n = connect(fd, sa, salen);
  if (n < 0) {
    perr_exit("connect error");
  }
  return n;
}
int Listen(int fd, int backlog)
{
  int n;
  if ((n = listen(fd, backlog)) < 0)
    perr_exit("listen error");
  return n;
}
int Socket(int family, int type, int protocol)
{
  int n;
  if ((n = socket(family, type, protocol)) < 0)
    perr_exit("socket error");
  return n;
}
ssize_t Read(int fd, void* ptr, size_t nbytes)
{
  ssize_t n;
again:
  if ((n = read(fd, ptr, nbytes)) == -1) {
    if (errno == EINTR)
      goto again;
    else
      return -1;
  }
  return n;
}
ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
  ssize_t n;
again:
  if ((n = write(fd, ptr, nbytes)) == -1) {
    if (errno == EINTR)
      goto again;
    else
      return -1;
  }
  return n;
}
int Close(int fd)
{
  int n;
  if ((n = close(fd)) == -1)
    perr_exit("close error");
  return n;
}
/*参三: 应该读取的字节数*/  
//socket 4096  readn(cfd, buf, 4096)   nleft = 4096-1500
ssize_t Readn(int fd, void* vptr, size_t n)
{
  size_t  nleft;              //usigned int 剩余未读取的字节数
  ssize_t nread;              //int 实际读到的字节数
  char* ptr;
  ptr = (char*)vptr;
  nleft = n;                  //n 未读取字节数
  while (nleft > 0) {
    if ((nread = read(fd, ptr, nleft)) < 0) {
      if (errno == EINTR)
        nread = 0;
      else
        return -1;
    }
    else if (nread == 0)
      break;
    nleft -= nread;   //nleft = nleft - nread 
    ptr += nread;
  }
  return n - nleft;
}
ssize_t Writen(int fd, const void* vptr, size_t n)
{
  size_t nleft;
  ssize_t nwritten;
  const char* ptr;
  ptr = (char*)vptr;
  nleft = n;
  while (nleft > 0) {
    if ((nwritten = write(fd, ptr, nleft)) <= 0) {
      if (nwritten < 0 && errno == EINTR)
        nwritten = 0;
      else
        return -1;
    }
    nleft -= nwritten;
    ptr += nwritten;
  }
  return n;
}
ssize_t My_read(int fd, char* ptr)
{
  static int read_cnt;
  static char* read_ptr;
  static char read_buf[100];
  if (read_cnt <= 0) {
  again:
    if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {   //"hello\n"
      if (errno == EINTR)
        goto again;
      return -1;
    }
    else if (read_cnt == 0)
      return 0;
    read_ptr = read_buf;
  }
  read_cnt--;
  *ptr = *read_ptr++;
  return 1;
}
/*readline --- fgets*/
//传出参数 vptr
ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
  ssize_t n, rc;
  char    c, * ptr;
  ptr = (char*)vptr;
  for (n = 1; n < maxlen; n++) {
    if ((rc = My_read(fd, &c)) == 1) {   //ptr[] = hello\n
      *ptr++ = c;
      if (c == '\n')
        break;
    }
    else if (rc == 0) {
      *ptr = 0;
      return n - 1;
    }
    else
      return -1;
  }
  *ptr = 0;
  return n;
}

error_handling.h

#ifndef __ERROR_HANDING__
#define __ERROR_HANDING__
//下面的所有替换socket网络编程函数,在函数内部都增加了输出错误信息的判断语句。
//打印输出错误信息
void perr_exit(const char* s);  
//替换Socket函数,并在内部调用perr_exit 函数,可以输出错误信息
int Socket(int family, int type, int protocol);
//替换Bind函数,并在内部调用perr_exit 函数,可以输出错误信息
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
//替换Listen函数,并在内部调用perr_exit 函数,可以输出错误信息
int Listen(int fd, int backlog);
//替换 accept 函数,并在内部调用perr_exit 函数,可以输出错误信息
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
//替换Connect函数,并在内部调用perr_exit 函数,可以输出错误信息
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
//替换Close函数,并在内部调用perr_exit 函数,可以输出错误信息
int Close(int fd);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t My_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);
#endif

遇到的问题,自定义函数显示未定义的引用。

server.c

20210506221332335.png

20210506221404785.png

问题分析:

Q1:先在 vs里面点击 server.cpp看能否跳转到我们自定义的错误函数处理部分。

A1::经测试发现能够直接跳转到我们自定义的错误处理函数,说明代码是没有错误的

Q2:是否是因为没有编译我们的 error_handling.cpp 导致的

测试:g++ error_handling.cpp -o error_handling

20210506221940343.png

这里面连 main函数都没有定义,你又怎么可能编译成功呢?

同类问题:

c++中如何在主函数中调用其他文件内的函数?

其实大家也看到了,我们需要编写Makefile了,哈哈。以前大家被vs惯坏了,既然现在大家要投入linux中那么就需要去编写自己的Makefile了。博主对此不详细描述。

解决方法:

不再和大家开玩笑了,其实这就是vs的好处了,但现在我们要在linux下运行,我们就需要用到我们大名鼎鼎makefil了,在这里博主不详细介绍Makefile的编写规则,需要的话可以留言博主会开一个博客专门介绍makefil。

编写我们的Makefile
src = $(wildcard *.c)
obj = $(patsubst %.c, %.o, $(src))
all: server client
server: server.o error_handling.o
  gcc server.o error_handling.o -o server -Wall
client: client.o error_handling.o
  gcc client.o error_handling.o -o client -Wall
%.o:%.c
  gcc -c $< -Wall
.PHONY: clean all
clean: 
  -rm -rf server client $(obj)

make

20210506230835104.png

测试:

正常测试:

20210506231119818.png

先关掉服务器,在关掉客户端马上在连接:

看到了吧,因为我们封装了错误处理函数,我们直接就能看到错误的原因。

20210506231158668.png

其余测试:

还有很多种测试方法大家可以,具体的方案大家可以修改server.cpp的Accept函数和Close函数,以及client.cpp 的Close函数的位置来进行多种方案的测试,博主在这里就不在演示了。


回声服务器的结语及下一部分的展望

至此我们的 回声服务器已经是完成了,不说超级牛逼,但也能吊打很多回声服务器了(毕竟我们完成了各个函数的出错处理的方案,不需要大家在一步一步调试信息的慢慢寻找了。)

下一部分内容:

目录
相关文章
|
6月前
010.第一个回声服务器可能遇到的问题——connect函数
由connect函数使用不当导致的小错误 博客说明: 在008.一个简单的网络服务器开发----回声服务器篇博客中我们实现了一个最为简单的回声服务器,当时我们为了突出程序的逻辑性,从而没有对部分函数进行错误检查那么博主在这里指出一个大家可能会遇到的一个经典错误,而且这个错误还不是太好排查的,那就是忽略了connect函数的返回值从而导致客户端发了消息但是服务器端并没有回应。
59 0
|
6月前
socket编程之回声服务器函数的陷阱
由connect函数使用不当导致的小错误 话不多说先看代码:
53 0
|
6月前
|
C语言
socket编程之回声服务器
回声服务器的实现 结合我们之前对socket函数的分析,现在做一个最简单的回声服务器(由客户端输入一串字符,再由服务器端转换成大写字符回显给客户端)
63 0
|
2月前
|
网络协议 数据处理 C语言
利用C语言基于poll实现TCP回声服务器的多路复用模型
此代码仅为示例,展示了如何基于 `poll`实现多路复用的TCP回声服务器的基本框架。在实际应用中,你可能需要对其进行扩展或修改,以满足具体的需求。
79 0
|
6月前
|
网络协议 Python
python中TCP回声服务器与客户端示例
【4月更文挑战第7天】本示例展示了TCP回声服务器和客户端的工作流程。服务器监听特定端口,接收客户端连接请求,接收数据并回显。客户端连接服务器,发送数据并接收回显。代码示例用Python实现,包括服务器的`socket.bind()`, `socket.listen()`, `socket.accept()`和客户端的`socket.connect()`, `socket.sendall()`, `socket.recv()`。运行示例时,先启动服务器再启动客户端,可观察TCP连接和数据传输过程。了解这些基础对于构建网络应用至关重要。
|
6月前
|
存储 网络协议 API
教你使用io_uring来写一个并发回声服务器
教你使用io_uring来写一个并发回声服务器
94 0
|
6月前
|
Linux C语言 C++
008.一个简单的网络服务器开发----回声服务器
简单的回声服务器实现 项目需求 实现回声服务器的客户端/服务器程序,客户端通过网络连接到服务器,并发送任意一串英文信息,服务器端接收信息后,将每个字符转换为大写并回送给客户端显示。
65 0
|
网络协议
【Socket】简单的回声服务器实现
【Socket】简单的回声服务器实现
【Socket】简单的回声服务器实现
|
1天前
|
存储 分布式计算 固态存储
阿里云2核16G、4核32G、8核64G配置云服务器租用收费标准与活动价格参考
2核16G、8核64G、4核32G配置的云服务器处理器与内存比为1:8,这种配比的云服务器一般适用于数据分析与挖掘,Hadoop、Spark集群和数据库,缓存等内存密集型场景,因此,多为企业级用户选择。目前2核16G配置按量收费最低收费标准为0.54元/小时,按月租用标准收费标准为260.44元/1个月。4核32G配置的阿里云服务器按量收费标准最低为1.08元/小时,按月租用标准收费标准为520.88元/1个月。8核64G配置的阿里云服务器按量收费标准最低为2.17元/小时,按月租用标准收费标准为1041.77元/1个月。本文介绍这些配置的最新租用收费标准与活动价格情况,以供参考。
|
7天前
|
弹性计算
阿里云2核16G服务器多少钱一年?亲测价格查询1个月和1小时收费标准
阿里云2核16G服务器提供多种ECS实例规格,内存型r8i实例1年6折优惠价为1901元,按月收费334.19元,按小时收费0.696221元。更多规格及详细报价请访问阿里云ECS页面。
39 9
下一篇
无影云桌面