012.成型版回声服务器

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

结合之前的博客我们已经知道了什么是网络编程以及如何通过调用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在这里插入图片描述
client.c
在这里插入图片描述

问题分析:

Q1:先在 vs里面点击 server.cpp看能否跳转到我们自定义的错误函数处理部分。
A1::经测试发现能够直接跳转到我们自定义的错误处理函数,说明代码是没有错误的

Q2:是否是因为没有编译我们的 error_handling.cpp 导致的
测试:g++ error_handling.cpp -o error_handling
在这里插入图片描述
这里面连 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

在这里插入图片描述

测试:

正常测试:

在这里插入图片描述

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

看到了吧,因为我们封装了错误处理函数,我们直接就能看到错误的原因。
在这里插入图片描述

其余测试:

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

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

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

下一部分内容:

开始进入新的章节了
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
回声服务器是一种简单的网络应用程序,它接收客户端的消息并将其返回给客户端。在Linux上创建回声服务器可以使用以下步骤: 1. 创建一个TCP套接字:使用socket函数创建一个TCP套接字。 2. 绑定套接字到一个IP地址和端口号:使用bind函数将套接字绑定到一个IP地址和端口号。 3. 监听连接请求:使用listen函数开始监听客户端的连接请求。 4. 接受连接:使用accept函数接受客户端的连接请求。 5. 接收客户端消息:使用recv函数接收客户端发送的消息。 6. 将接收到的消息返回给客户端:使用send函数将接收到的消息返回给客户端。 7. 关闭连接:使用close函数关闭连接。 下面是一个简单的回声服务器代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #define MAX_MSG_LEN 1024 int main(int argc, char **argv) { if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); return -1; } int port = atoi(argv[1]); int server_sock = socket(AF_INET, SOCK_STREAM, 0); if (server_sock == -1) { perror("socket"); return -1; } struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(port); if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); return -1; } if (listen(server_sock, 5) == -1) { perror("listen"); return -1; } printf("Echo server started on port %d.\n", port); while (1) { struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len); if (client_sock == -1) { perror("accept"); return -1; } char buf[MAX_MSG_LEN]; ssize_t n = recv(client_sock, buf, MAX_MSG_LEN, 0); if (n == -1) { perror("recv"); return -1; } buf[n] = '\0'; printf("Received message from %s:%d: %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf); n = send(client_sock, buf, n, 0); if (n == -1) { perror("send"); return -1; } close(client_sock); } close(server_sock); return 0; } ``` 编译并运行该代码,即可启动一个简单的回声服务器。例如,可以使用以下命令将服务器启动在本地的8888端口: ``` $ gcc -o echo_server echo_server.c $ ./echo_server 8888 Echo server started on port 8888. ``` 然后,可以使用telnet命令连接到服务器并发送消息: ``` $ telnet localhost 8888 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Hello, world! Hello, world! Connection closed by foreign host. ``` 可以看到,服务器接收到了客户端发送的消息,并将其返回给了客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值