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版本还是有很大不足的,以后将慢慢改进。