Linux网络编程——简单的TCP网络通信
目录
前言
TCP套接字接口
简单的TCP网络通信
.hpp文件
打印日志文件 logMessage.hpp
TCP服务端实现
1、listen()
2、 accept()
完整代码
TCP客户端的实现
connect()接口
完整代码
多进程版本一
多进程版本二
前言
上篇文章中我们对udp网络通信有了初步的认识,我们在之前已经知道了UDP通信和TCP通信的不同
- UDP非连接,面向数据包。
- TCP连接,面向字节流。
这篇文章我们来介绍和演示一下TCP套接字的接口。
TCP套接字接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr* address, socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
// 发送报文 (UDP)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
// 接收报文 (UDP)
ssize_t recvfrom(int socket, void* restrict buffer, size_t length, int flags, struct sockaddr* restrict address, socklen_t* restrict address_len);
上篇文中我们已经演示过了里面的一些接口,剩下的三个用于连接的接口就在下面演示一遍。
简单的TCP网络通信
在学习过UDP之后,TCP这里出了新的接口之外,实际上并没有多少新的东西,无非就是连接的细节不一样罢了,下面直接演示:
.hpp文件
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "logMessage.hpp"
#define SOCKET_ERR 1
#define BIND_ERR 2
#define LISTEN_ERR 3
#define USE_ERR 4
#define CONNECT_ERR 5
#define FORK_ERR 6
#define BUFFER_SIZE 1024
打印日志文件 logMessage.hpp
与UDP的打印文件相同,不做过多解释
#pragma once
#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <cstdlib>
// 宏定义 四个日志等级
#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3
const char* log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};
// 实现一个 可以输出: 日志等级、日志时间、用户、以及相关日志内容的 日志消息打印接口
void logMessage(int level, const char* format, ...) {
// 通过可变参数实现, 传入日志等级, 日志内容格式, 日志内容相关参数
// 确保日志等级正确
assert(level >= DEBUG);
assert(level <= FATAL);
// 获取当前用户名
char* name = getenv("USER");
// 简单的定义log缓冲区
char logInfo[1024];
// 定义一个指向可变参数列表的指针
va_list ap;
// 将 ap 指向可变参数列表中的第一个参数, 即 format 之后的第一个参数
va_start(ap, format);
// 此函数 会通过 ap 遍历可变参数列表, 然后根据 format 字符串指定的格式, 将ap当前指向的参数以字符串的形式 写入到logInfo缓冲区中
vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);
// ap 使用完之后, 再将 ap置空
va_end(ap); // ap = NULL
// 通过判断日志等级, 来选择是标准输出流还是标准错误流
FILE* out = (level == FATAL) ? stderr : stdout;
// 获取本地时间
time_t tm = time(nullptr);
struct tm* localTm = localtime(&tm);
char* localTmStr = asctime(localTm);
char* nC = strstr(localTmStr, "\n");
if(nC) {
*nC = '\0';
}
fprintf( out, "%s | %s | %s | %s\n",
log_level[level],
localTmStr,
name == nullptr ? "unknow" : name,
logInfo );
}
TCP服务端实现
我们还是由浅入深,实现一个简单点的TCP服务器,服务器包含一个对客户端发送来的消息进行小写转大写的功能。要实现TCP网络通信服务端都需要哪些步骤,与UDP的区别具体是什么呢?
- 创建套接字:使用
socket()
函数创建一个新的套接字。 - 绑定地址和端口:通过
bind()
将该套接字绑定到一个特定的IP地址和端口号上。 - 监听连接请求:调用
listen()
函数让套接字进入监听状态,准备接受连接请求。 - 接受连接:使用
accept()
函数接受来自客户端的连接请求。这将返回一个新的套接字描述符,用于与特定客户端进行通信。 - 数据交换:使用
send()
和recv()
(或write()
和read()
)函数来发送和接收数据。 - 关闭连接:通信完成后,使用
close()
关闭连接。
可以看到TCP通信服务端与UDP通信服务端的主要差别在于监听连接请求、接收连接这两步。
先来分别介绍一下这几个新的接口
1、listen()
int listen(int sockfd, int backlog);
与UDP不同,TCP网络通信是双方要以建立连接为基础的。在TCP通信中,listen 是服务器端用于开始监听传入连接请求的一个重要函数。它是网络编程中实现服务端逻辑的关键步骤之一。
接口参数:
- sockfd: 这是一个整数类型的文件描述符,指向之前通过调用 socket() 创建的套接字。这个套接字需要绑定到一个本地地址(使用 bind() 函数)。
- backlog: 它指定了待处理连接请求队列的最大长度。也就是说,它决定了系统可以为该套接字排队等待接受的最大未处理连接数。如果连接请求数超过了这个值,新的请求可能会被拒绝。(类似餐馆就像是服务器,而座位和等候区加起来就像是 backlog 参数。)
返回值:
- 成功时,listen() 返回 0。
- 错误时,返回 -1,并设置全局变量 errno 来指示错误类型。
执行
listen()
, 服务器会自动进入监听状态. 之后会一直监听 来自客户端 向服务器 发送的连接请求. 实际上监听的就是服务器的套接字. 监听的过程是非阻塞的.
2、 accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP是 面向连接 的. 其他客户端想要将信息发送到服务器, 就必须先于服务器建立连接.
而能否与服务器建立连接 是服务器说了算的. 只有 服务器接受了来自客户端的连接请求 才是连接成功 了。
接口参数:
- sockfd: 这是一个整数类型的文件描述符,指向之前通过调用 socket() 创建并且已经通过 bind() 和 listen() 设置为监听状态的套接字。
- addr: 这是一个指向 struct sockaddr 类型对象的指针,用于存储发起连接的客户端地址信息(如果不需要获取客户端地址信息,可以传递 NULL)。
- addrlen: 这是一个指向 socklen_t 类型变量的指针,指示 addr 参数指向的对象的大小。在调用 accept 函数前,应设置为该结构体的大小;函数返回时,会更新为实际写入的地址长度。
返回值:
- 成功时,accept() 返回一个新的套接字描述符,用于和客户端进行通信。
- 错误时,返回 -1,并设置全局变量
errno
来指示错误类型。
accept 是在服务器端用于接受来自客户端的连接请求的一个重要函数。它通常与
listen
函数配合使用,用来从已完成连接队列中提取第一个连接请求,创建一个新的套接字,并返回引用这个套接字的文件描述符,这个新的套接字不会被监听,服务器原来的套接字,不会被此次调用影响。也就是说,这个TCP服务器会创建很多个套接字,而UDP只会创建一个属于服务器的套接字。这个新创建的套接字和原本的套接字有什么关系呢?
服务器原本的套接字已经被listen接口作用,处于监听状态,是为了能够接收到连接信息。当accept接受了客户端的连接请求时,会创建一个存储有 服务器本地网络信息 以及 客户端网络信息的新的套接字,服务器可以通过此套接字与客户端进行通信。
所以原来的服务器创建的套接字 只用来与客户端建立连接,所以被称为 监听套接字。
创建的新的套接字存储有 服务器本地网络信息 以及 客户端网络信息,并且 此套接字也会被绑定到系统内核中,并于服务器原套接字相独立。此套接字主要是用来给客户端提供服务的套接字。
完整代码
//tcpServer.cc
#include"tcp.hpp"
using std::string;
using std::cerr;
using std::endl;
class tcpServer
{
public:
tcpServer(uint16_t port,const string& ip="")//按引用传递提高效率
:_port(port)
,_ip(ip){}
~tcpServer()
{}
void Init()
{//面向字节流参数选择SOCK_STREAM
_LiSockFd=socket(AF_INET,SOCK_STREAM,0);//创建套接字(先创建一个listen的套接字)
if(_LiSockFd<0)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(SOCKET_ERR);
}
logMessage(DEBUG,"socket() success::%d",_LiSockFd);
struct sockaddr_in local;
memset(&local,0,sizeof(local));//初始化结构体
local.sin_family=AF_INET;//填充
local.sin_port=htons(_port);
_ip.empty()?(local.sin_addr.s_addr=htonl(INADDR_ANY)):(inet_aton(_ip.c_str(),&local.sin_addr));
if(bind(_LiSockFd,(const struct sockaddr*)&local,sizeof(local))==-1)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(BIND_ERR);
}
logMessage(DEBUG,"bind() success::%d",_LiSockFd);
if(listen(_LiSockFd,5)==-1)//执行listen(), 服务器会自动进入监听状态. 之后会一直监听 来自客户端 向服务器 发送的连接请求. 实际上监听的就是服务器的套接字. 监听的过程是非阻塞的.
{
logMessage(FATAL,"listen() failed::%s : %d",strerror(errno),_LiSockFd);
exit(LISTEN_ERR);
}
logMessage(DEBUG,"listen() success::%d",_LiSockFd);
}
void to_upper(int sock,const string& clientIP,const uint16_t &clientPort)//小写转大写功能函数
{
assert(sock>0);
assert(!clientIP.empty());
char inbuffer[BUFFER_SIZE];//用于存储来自客户端信息的数组
while(true)
{
// TCP获取来自客户端的信息的操作就是 read
// 从 服务器与客户端连接 的文件描述符中 读取来自客户端的信息
// 可看作 通过文件描述符 从文件读取内容
ssize_t s=read(sock,inbuffer,sizeof(inbuffer)-1);
if(s>0)
{
inbuffer[s]='\0';
// 我们实现一个操作, 如果 客户端传输过来的信息是 quit 这个单词, 就表示客户端请求退出
// 就可以退出 服务循环了
if(strcasecmp(inbuffer,"quit")==0)
{
logMessage(DEBUG,"Client requests to quit: [%s: %d]",clientIP.c_str(),clientPort);
break;
}
logMessage(DEBUG,"to_upper before:[%s:%d]>> %s",clientIP.c_str(),clientPort,inbuffer);
for(int i=0;i<s;i++)
{
if(isalpha(inbuffer[i])&&islower(inbuffer[i]))
inbuffer[i]=toupper(inbuffer[i]);
}
logMessage(DEBUG,"to_upper after:[%s:%d]>>%s",clientIP.c_str(),clientPort,inbuffer);
// 上面做的都是对获取到的信息 进行转换
// 最后需要做的就是 将转换后的信息 再重新回应给客户端
// 而 回应给客户端 则是用 write, 可看做 通过文件描述符像文件写入内容
write(sock,inbuffer,strlen(inbuffer));
}
else if(s==0)
{
logMessage(DEBUG,"Client has quited:[%s:%d],clientIP,clientPort");
break;
}
else{
// 到这里 本次 read() 出错
logMessage(DEBUG,"Client [%s:%d] read::%s",clientIP.c_str(),clientPort,strerror(errno));
break;
}
}
// 走到这里 循环已经退出了, 表示 client 也已经退出了
// 所以 此时需要关闭文件描述符, 因为一个主机上的文件描述符数量是一定的, 达到上限之后 就无法再创建
// 已经无用但没有被归还的文件描述符, 文件描述符泄漏
close(sock);
logMessage(DEBUG,"Service close %d sockFd",sock);
}
void start()
{
while(true)
{
struct sockaddr_in peer;
socklen_t peerlen=sizeof(peer);
int serviceSockFd=accept(_LiSockFd,(struct sockaddr*)&peer,&peerlen);
if(serviceSockFd==-1)
{
logMessage(WARINING,"accept() failed::%s : %d",strerror(errno),_LiSockFd);
continue;
}
string peerIp=inet_ntoa(peer.sin_addr);
uint16_t peerPort=ntohs(peer.sin_port);
logMessage(DEBUG,"accept() success:: [%s: %d] | %d",peerIp.c_str(),peerPort,serviceSockFd);
to_upper(serviceSockFd,peerIp,peerPort);//执行转换功能,小写换大写
}
}
private:
uint16_t _port;//存储服务器端口号
string _ip;//存储服务器ip
int _LiSockFd;//存储服务器进程的监听套接字
};
void Usage(string proc)
{
cerr<<"Usage::\n\t"<<proc<<"port ip"<<endl;
cerr<<"example::\n\t"<<proc<<"8080 127.0.0.1"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3&&argc!=2)
{
Usage(argv[0]);
exit(USE_ERR);
}
uint16_t port=atoi(argv[1]);
string ip;
if(argc==3)
{
ip=argv[2];
}
tcpServer Usvr(port,ip);
Usvr.Init();
Usvr.start();
return 0;
}
TCP是一种面向流的协议,所以可以直接使用 read() 和 write() 通过文件描述符与客户端进行通信。
TCP客户端的实现
- 创建套接字:同样使用
socket()
创建一个新的套接字。 - 连接到服务器:使用
connect()
函数尝试连接到指定的服务器地址和端口。 - 数据交换:使用
send()
和recv()
函数发送和接收数据。 - 关闭连接:完成通信后,使用
close()
关闭连接。
connect()接口
在网络编程中,该接口用于客户端连接到服务器。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
: 这是一个整数类型的文件描述符,指向之前通过调用socket()
创建的套接字。addr
: 这是一个指向struct sockaddr
类型对象的指针,包含了你要连接的服务器的地址信息(包括IP地址和端口号)。addrlen
: 这是一个socklen_t
类型的值,表示第二个参数addr
指向的对象的大小。同样的,connect() 也会和 sendto() 的那样,自动绑定客户端的网络信息。
等连接成功之后,就可以使用 write() 向服务器发送信息 ,使用 read() 读取服务器的回复了。这个过程中使用的文件描述符都是客户端套接字的文件描述符。
完整代码
//tcpClient.cc
#include"tcp.hpp"
using std::cerr;
using std::endl;
using std::string;
using std::cin;
using std::getline;
using std::cout;
volatile bool quit=false;
void Usage( string proc)
{
cerr<<"Usage::\n\t"<<proc<<"serverIp serverPort"<<endl;
cerr<<"example::\n\t"<<proc<<"127.0.0.1 8080"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(USE_ERR);
}
string serverIp=argv[1];
uint16_t serverPort=atoi(argv[2]);
int sockFd=socket(AF_INET,SOCK_STREAM,0);
if(sockFd<0)
{
logMessage(FATAL,"socket() failed::%s %d",strerror(errno),sockFd);
exit(SOCKET_ERR);
}
logMessage(DEBUG,"socket() success::%d",sockFd);
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_port=serverPort;
server.sin_family=AF_INET;
inet_aton(serverIp.c_str(),&server.sin_addr);
if(connect(sockFd,(const sockaddr*)&server,sizeof(server))==-1)
{
logMessage(FATAL,"connect() failed::%s: %d",strerror(errno),sockFd);
exit(CONNECT_ERR);
}
logMessage(DEBUG,"connect() success.");
string message;
while(!quit)
{
message.clear();
cout<<"Please enter";
getline(cin,message);
if(strcasecmp(message.c_str(),"quit")==0)
{
quit=true;
}
ssize_t sw=write(sockFd,message.c_str(),message.size());
if(sw>0)
{
message.resize(BUFFER_SIZE);
ssize_t sr=read(sockFd,(char*)message.c_str(),BUFFER_SIZE);
if(sr>0)
{
message[sr]='\0';
}
if(strcasecmp(message.c_str(),"quit"))
{
cout<<"Server Echo"<<message<<endl;
}
}
else if(sw<0)
{
logMessage(FATAL,"Client write() failed: %d : %s",sockFd,strerror(errno));
break;
}
}
close(sockFd);
return 0;
}
但是以上的版本是单进程版本,当多客户尝试连接服务器时,会发现服务器只会对第一个连接到客户端进行响应。当客户端连接到服务器的时候,会调用 to_upper() 这个函数,但是它本质上是一个死循环,所以单进程进去之后就出不来了,后面的客户无论怎么连接都是无法响应的, 因为进程的唯一一个执行流在死循环内,这里就需要用到多进程或者多线程来处理这个问题了。
多进程版本一
我们只需要在刚刚出问题的地方做修改就好了,即在 to_upper() 这个函数这里。只需要在该函数之前创建子进程,将这个函数放在子进程里面去执行。
#include"tcp.hpp"
using std::string;
using std::cerr;
using std::endl;
class tcpServer
{
public:
tcpServer(uint16_t port,const string& ip="")
:_port(port)
,_ip(ip){}
~tcpServer()
{}
void Init()
{
_LiSockFd=socket(AF_INET,SOCK_STREAM,0);
if(_LiSockFd<0)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(SOCKET_ERR);
}
logMessage(DEBUG,"socket() success::%d",_LiSockFd);
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
_ip.empty()?(local.sin_addr.s_addr=htonl(INADDR_ANY)):(inet_aton(_ip.c_str(),&local.sin_addr));
if(bind(_LiSockFd,(const struct sockaddr*)&local,sizeof(local))==-1)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(BIND_ERR);
}
logMessage(DEBUG,"bind() success::%d",_LiSockFd);
if(listen(_LiSockFd,5)==-1)
{
logMessage(FATAL,"listen() failed::%s : %d",strerror(errno),_LiSockFd);
exit(LISTEN_ERR);
}
logMessage(DEBUG,"listen() success::%d",_LiSockFd);
}
void to_upper(int sock,const string& clientIP,const uint16_t &clientPort)
{
assert(sock>0);
assert(!clientIP.empty());
char inbuffer[BUFFER_SIZE];
while(true)
{
ssize_t s=read(sock,inbuffer,sizeof(inbuffer)-1);
if(s>0)
{
inbuffer[s]='\0';
if(strcasecmp(inbuffer,"quit")==0)
{
logMessage(DEBUG,"Client requests to quit: [%s: %d]",clientIP.c_str(),clientPort);
break;
}
logMessage(DEBUG,"to_upper before:[%s:%d]>> %s",clientIP.c_str(),clientPort,inbuffer);
for(int i=0;i<s;i++)
{
if(isalpha(inbuffer[i])&&islower(inbuffer[i]))
inbuffer[i]=toupper(inbuffer[i]);
}
logMessage(DEBUG,"to_upper after:[%s:%d]>>%s",clientIP.c_str(),clientPort,inbuffer);
write(sock,inbuffer,strlen(inbuffer));
}
else if(s==0)
{
logMessage(DEBUG,"Client has quited:[%s:%d],clientIP,clientPort");
break;
}
else{
logMessage(DEBUG,"Client [%s:%d] read::%s",clientIP.c_str(),clientPort,strerror(errno));
break;
}
}
close(sock);
logMessage(DEBUG,"Service close %d sockFd",sock);
}
void start()
{
signal(SIGCHLD,SIG_IGN);
while(true)
{
struct sockaddr_in peer;
socklen_t peerlen=sizeof(peer);
int serviceSockFd=accept(_LiSockFd,(struct sockaddr*)&peer,&peerlen);
if(serviceSockFd==-1)
{
logMessage(WARINING,"accept() failed::%s : %d",strerror(errno),_LiSockFd);
continue;
}
string peerIp=inet_ntoa(peer.sin_addr);
uint16_t peerPort=ntohs(peer.sin_port);
logMessage(DEBUG,"accept() success:: [%s: %d] | %d",peerIp.c_str(),peerPort,serviceSockFd);
pid_t id=fork();
if(id==-1)
{
logMessage(FATAL,"Server fork() failed:%s",strerror(errno));
exit(FORK_ERR);
}
else if(id==0)
{
// 进入子进程
// 子进程会继承 父进程的文件描述符表, 但是这已经是两个不同的进程了
// 所以建议进入子进程之后, 先关闭_LiSockFd, 防止子进程代码可能对此文件造成影响
close(_LiSockFd);
to_upper(serviceSockFd,peerIp,peerPort);
exit(0);
}
close(serviceSockFd);
// 父进程需要手动关闭 连接客户端创建的与客户端通信的文件描述符
// 如果不关闭父进程会发生文件描述符泄漏
// 父子进程在各子进程中关闭某文件描述符 是不影响对方的
}
}
private:
uint16_t _port;
string _ip;
int _LiSockFd;
};
void Usage(string proc)
{
cerr<<"Usage::\n\t"<<proc<<"port ip"<<endl;
cerr<<"example::\n\t"<<proc<<"8080 127.0.0.1"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3&&argc!=2)
{
Usage(argv[0]);
exit(USE_ERR);
}
uint16_t port=atoi(argv[1]);
string ip;
if(argc==3)
{
ip=argv[2];
}
tcpServer Usvr(port,ip);
Usvr.Init();
Usvr.start();
return 0;
}
需要注意的问题:
- 子进程是继承父进程的文件描述符表的,也就是说父进程已经打开的文件在子进程继承的时候是默认打开的。
- _LiSockFd 是监听套接字,对于子进程来说是没用的,所以提前关掉以免子进程的执行影响到父进程的监听。
- serviceSockFd是服务端与客户端用来通信的新的套接字,使用多进程后变成了子进程实现通信功能,该套接字的文件描述符的关闭操作原本是在 to_upper() 函数中的,现在在子进程中调用此函数,所以子进程内不用再手动关闭了。但是, 父进程已经不再执行to_upper(), 所以必须要 close(serviceSockFd) . 否则父进程会发生文件描述符泄漏
- 子进程退出时, 是需要父进程回收的.如果父进程使用 wait() 进行等待, 默认是阻塞式的 无法使用. 如果使用 waitpid() 非阻塞, 也需要存储所有子进程的
pid
, 很麻烦.所以, 我们直接在 start() 函数刚开始 执行 signal(SIGCHLD,SIG_IGN);
忽略子进程的退出信号. 这样子进程退出时, 会被自动回收.
多进程版本二
这个版本不在忽略子进程的退出信号. 而是利用了进程的一个特性.
如果子进程还没有退出, 但是其父进程退出了, 那么此子进程就成了孤儿进程, 会被操作系统领养. 退出时会自动被操作系统回收
即, 主父进程创建子进程之后, 子进程又创建了孙子进程. 然后子进程退出, 让孙子进程与客户端通信. 主父进程直接回收退出的子进程. 也不会发生一直阻塞等待的情况.
#include"tcp.hpp"
using std::string;
using std::cerr;
using std::endl;
class tcpServer
{
public:
tcpServer(uint16_t port,const string& ip="")
:_port(port)
,_ip(ip){}
~tcpServer()
{}
void Init()
{
_LiSockFd=socket(AF_INET,SOCK_STREAM,0);
if(_LiSockFd<0)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(SOCKET_ERR);
}
logMessage(DEBUG,"socket() success::%d",_LiSockFd);
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
_ip.empty()?(local.sin_addr.s_addr=htonl(INADDR_ANY)):(inet_aton(_ip.c_str(),&local.sin_addr));
if(bind(_LiSockFd,(const struct sockaddr*)&local,sizeof(local))==-1)
{
logMessage(FATAL,"socket() failed::%s : %d",strerror(errno),_LiSockFd);
exit(BIND_ERR);
}
logMessage(DEBUG,"bind() success::%d",_LiSockFd);
if(listen(_LiSockFd,5)==-1)
{
logMessage(FATAL,"listen() failed::%s : %d",strerror(errno),_LiSockFd);
exit(LISTEN_ERR);
}
logMessage(DEBUG,"listen() success::%d",_LiSockFd);
}
void to_upper(int sock,const string& clientIP,const uint16_t &clientPort)
{
assert(sock>0);
assert(!clientIP.empty());
char inbuffer[BUFFER_SIZE];
while(true)
{
ssize_t s=read(sock,inbuffer,sizeof(inbuffer)-1);
if(s>0)
{
inbuffer[s]='\0';
if(strcasecmp(inbuffer,"quit")==0)
{
logMessage(DEBUG,"Client requests to quit: [%s: %d]",clientIP.c_str(),clientPort);
break;
}
logMessage(DEBUG,"to_upper before:[%s:%d]>> %s",clientIP.c_str(),clientPort,inbuffer);
for(int i=0;i<s;i++)
{
if(isalpha(inbuffer[i])&&islower(inbuffer[i]))
inbuffer[i]=toupper(inbuffer[i]);
}
logMessage(DEBUG,"to_upper after:[%s:%d]>>%s",clientIP.c_str(),clientPort,inbuffer);
write(sock,inbuffer,strlen(inbuffer));
}
else if(s==0)
{
logMessage(DEBUG,"Client has quited:[%s:%d],clientIP,clientPort");
break;
}
else{
logMessage(DEBUG,"Client [%s:%d] read::%s",clientIP.c_str(),clientPort,strerror(errno));
break;
}
}
close(sock);
logMessage(DEBUG,"Service close %d sockFd",sock);
}
void start()
{
while(true)
{
struct sockaddr_in peer;
socklen_t peerlen=sizeof(peer);
int serviceSockFd=accept(_LiSockFd,(struct sockaddr*)&peer,&peerlen);
if(serviceSockFd==-1)
{
logMessage(WARINING,"accept() failed::%s : %d",strerror(errno),_LiSockFd);
continue;
}
string peerIp=inet_ntoa(peer.sin_addr);
uint16_t peerPort=ntohs(peer.sin_port);
logMessage(DEBUG,"accept() success:: [%s: %d] | %d",peerIp.c_str(),peerPort,serviceSockFd);
pid_t id=fork();
if(id==-1)
{
logMessage(FATAL,"Server fork() failed:%s",strerror(errno));
exit(FORK_ERR);
}
else if(id==0)
{
close(_LiSockFd);
if(fork()>0)
{
exit(0);
}
to_upper(serviceSockFd,peerIp,peerPort);
exit(0);
}
close(serviceSockFd);
int ret=waitpid(id,nullptr,peerPort);
if(ret==-1)
{
logMessage(FATAL,"Server waitpid failed:%s",strerror(errno));
exit(WAIT_ERR);
}
(void)ret;
}
}
private:
uint16_t _port;
string _ip;
int _LiSockFd;
};
void Usage(string proc)
{
cerr<<"Usage::\n\t"<<proc<<"port ip"<<endl;
cerr<<"example::\n\t"<<proc<<"8080 127.0.0.1"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3&&argc!=2)
{
Usage(argv[0]);
exit(USE_ERR);
}
uint16_t port=atoi(argv[1]);
string ip;
if(argc==3)
{
ip=argv[2];
}
tcpServer Usvr(port,ip);
Usvr.Init();
Usvr.start();
return 0;
}