Linux服务器开发(基础篇):聊天服务器1.0版本实现

引言

上篇文章中,笔者讲解了与socket编程相关的一系列常用函数的功能与参数,在本节中,我们一起来实现一个简单的聊天服务器,来加深对socket编程的理解。

各部分封装代码及其讲解

common.h中的函数及其功能

#ifndef _COMMON_H_
#define _COMMON_H_

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<errno.h>
#include<stdlib.h>

#define ERROR_LOG 0
//端口号
#define SERVER_PORT 6666
//单条信息最大字节数
#define MESSAGE_MAX_LEN 256

//打印出错信息 可考虑log4cpp输出生成日志
void Error();
//函数指针数组 错误处理 待拓展
typedef void (*ErrorHandle[])() ;
ErrorHandle handle = {
    
    Error};


//以下函数均对错误进行处理
//创建socket
int Socket(int domain=AF_INET,int type=SOCK_STREAM);
//绑定
void Bind(int sockfd,struct sockaddr_in*addr);
//监听 默认最大待处理请求数为128 
void Listen(int sockfd,int backlog=128);
//服务器接受连接
int Accept(int sockfd,struct sockaddr_in*addr);
//客户端连接服务器
void Connect(int sockfd,struct sockaddr_in*addr);

void Error(){
    
    
	fprintf(stderr,"error occurred!\nreason:%s\terrno:%d\n",strerror(errno),errno);
	_exit(-errno);
}

int Socket(int domain,int type)
{
    
    
	int sockfd = socket(domain,type,0);
	if(sockfd == -1)
	{
    
    
		handle[ERROR_LOG]();
	}
	return sockfd;
}

void Bind(int sockfd,struct sockaddr_in*addr)
{
    
    
	if(bind(sockfd, (struct sockaddr *)addr,  sizeof(*addr))==-1)
	{
    
    
		handle[ERROR_LOG]();
	}
}

void Listen(int sockfd,int backlog)
{
    
    
	if(listen(sockfd, backlog)==-1)
	{
    
    
		handle[ERROR_LOG]();
	}
}

int Accept(int sockfd,struct sockaddr_in*addr)
{
    
    
	socklen_t addrlen = sizeof(*addr);
	int clientSock = accept(sockfd,(struct sockaddr*)addr,&addrlen);
	if(clientSock==-1)
	{
    
    
		handle[ERROR_LOG]();
	}
	return clientSock;
}

void Connect(int sockfd,struct sockaddr_in*addr)
{
    
    
	if(connect(sockfd,(struct sockaddr*)addr,sizeof(*addr))==-1)
	{
    
    
		handle[ERROR_LOG]();
	}
}

#endif //end

该头文件中封装了很多共性的功能,在服务器与客户端代码中直接使用即可。
echo_server_v1.cpp文件代码如下:

#include "common.h"

int main(void)
{
    
    
	int serverSock = Socket();
	struct sockaddr_in serverAddr;
	bool flag = true;
	bzero(&serverAddr,sizeof(serverAddr));
	//添加端口号以及监听信息
	serverAddr.sin_family = AF_INET;
	//监听本地所有IP地址
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serverAddr.sin_port = htons(SERVER_PORT);
	//套接字与各种信息的绑定
	Bind(serverSock,&serverAddr);
	//上限可以在linux系统文件中设置
	Listen(serverSock);
	
	printf("等待客户端连接请求......\n");
	
	//此版本每个客户端只与服务器进行两次交互
	while(flag)
	{
    
    
        struct sockaddr_in client;
        int clientSock, recvLen, sendLen,realSendLen;
        char clientIp[64];
        char buf[MESSAGE_MAX_LEN];
		clientSock = Accept(serverSock,&client);
		printf("client ip: %s\t port : %d has connected\n",
                 inet_ntop(AF_INET, &client.sin_addr.s_addr,clientIp,sizeof(clientIp)),
                 ntohs(client.sin_port));
		//通过套接字接收、发送信息
		recvLen = read(clientSock, buf,sizeof(buf)-1);
		buf[recvLen]='\0';
		printf("recv msg:%s\tlength:%d\tfrom ip:%s\n",buf,recvLen,clientIp);
		//从标准输入要发送的信息
		sendLen = read(0,buf,sizeof(buf)-1);
		buf[sendLen]='\0';
		realSendLen = write(clientSock, buf ,sendLen);
		//对比实际发送长度与输入长度
	printf("sendLen:%d\trealSendLen:%d\n",sendLen,realSendLen);
		//回收资源
		close(clientSock);
	}
	//回收
	close(serverSock);
	
	return 0;
}

echo_client_v1.cpp代码:

#include "common.h"
#include <netinet/in.h>

#define SERVER_IP  "127.0.0.1"

int main(void)
{
    
    
	int clientSock = Socket();
	struct sockaddr_in serverAddr;
	memset(&serverAddr,0,sizeof(serverAddr));
	//设置服务器的相关信息
	serverAddr.sin_family = AF_INET;
	inet_pton(AF_INET,SERVER_IP,&serverAddr.sin_addr);
	serverAddr.sin_port = htons(SERVER_PORT);
	//与服务器进行连接
	Connect(clientSock,&serverAddr);
	char buf[MESSAGE_MAX_LEN];
	int sendLen,realSendLen,recvLen;
	//read不会加结束符 结尾还会多一个\n
	sendLen = read(0,buf,sizeof(buf)-1);
	buf[sendLen]='\0';
	realSendLen = write(clientSock,buf,sendLen);
	printf("sendLen:%d\trealSendLen:%d\n",sendLen,realSendLen);

	recvLen = read(clientSock,buf,sizeof(buf)-1);
	buf[recvLen]='\0';
	printf("recv:%s\tlength:%d\tfrom server!\n",buf,recvLen);
	return 0;
}

码完代码,接下来就是编译运行了,我使用的Centos 7 版本,g++编译,语句如下图:在这里插入图片描述
编译成功后,本终端运行服务器,新开一个终端做客户端(想开几个开几个),运行即可。
截图如下:
在这里插入图片描述
可以多开几个客户端终端来试验一下。可以看出,1.0版本还是有很大不足的,以后将慢慢改进。

猜你喜欢

转载自blog.csdn.net/weixin_45416439/article/details/125992519