socket通讯的客户端。
* 此程序用于演示socket通讯的客户端。
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
if (argc!=3)
{
printf("Using:./demo01 ip port\nExample:./demo01 127.0.0.1 5005\n\n"); return -1;
}
// 第1步:创建客户端的socket。
int sockfd;
if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
// 第2步:向服务器发起连接请求。
struct hostent* h;
if ( (h = gethostbyname(argv[1])) == 0 ) // 指定服务端的ip地址。
{ printf("gethostbyname failed.\n"); close(sockfd); return -1; }
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通讯端口。
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
//把socket连接设置为非阻塞,此时不等待3次握手完成connect就直接返回,
fcntl(sockfd, F_SETFL, O_NONBLOCK);
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) // 向服务端发起连接清求。
{
printf("errno=%d, EINPROGRESS=%d\n", errno,EINPROGRESS); //非阻塞socket连接,立即返回的错误码
if( errno != EINPROGRESS)
{
perror("connect"); close(sockfd); return -1;
}
}
/*
int bufsize= 0;
socklent_t optlen=sizeof(bufsize);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen); //获取接收缓存区的大小
printf("recv bufsize=%d\n", bufsize);
getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen); //获取发送缓存区的大小
printf("recv bufsize=%d\n", bufsize);
*/
int iret;
char buffer[102400];
// 第3步:与服务端通讯,发送一个报文后等待回复,然后再发下一个报文。
for (int ii=0;ii<10;ii++)
{
memset(buffer,0,sizeof(buffer));
sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
{ perror("send"); break; }
printf("发送:%s\n",buffer);
//因为上面已经将socket设置为非阻塞,所以当服务端的报文没来得及发回来时,这里接收就失败了,返回-1
//这种失败不是socket失败,而是没有数据可读。返回了EAGAIN的错误码
//非阻塞的IO一般是在IO复用的场景下,所以需要通过select, poll, epoll方式处理一下
struct pollfd fds;
fds.fd = sockfd;
fds.events=POLLIN;
poll(&fds, 1, -1); //超时时间设置-1,直到有事件发生才返回。
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // 接收服务端的回应报文。
{
printf("iret=%d, erron=%d, EAGAIN=%d\n",iret, errno, EAGAIN); break;
}
printf("接收:%s\n",buffer);
sleep(1); // 每隔一秒后再次发送报文。
}
// 第4步:关闭socket,释放资源。
close(sockfd);
}
socket通讯的服务端
* 此程序用于演示socket通讯的服务端。
*
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
if (argc!=2)
{
printf("Using:./demo02 port\nExample:./demo02 5005\n\n"); return -1;
}
// 第1步:创建服务端的socket。
int listenfd;
if ( (listenfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
// 第2步:把服务端用于通讯的地址和端口绑定到socket上。
struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通讯端口。
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
{ perror("bind"); close(listenfd); return -1; }
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
// 第3步:把socket设置为监听模式。
if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }
// 第4步:接受客户端的连接。
int clientfd; // 客户端的socket。
int socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr; // 客户端的地址信息。
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
printf("客户端(%s)已连接。\n",inet_ntoa(clientaddr.sin_addr));
int iret;
char buffer[102400];
// 第5步:与客户端通讯,接收客户端发过来的报文后,回复ok。
while (1)
{
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) // 接收客户端的请求报文。
{
printf("iret=%d\n",iret); break;
}
printf("接收:%s\n",buffer);
strcpy(buffer,"ok");
if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) // 向客户端发送响应结果。
{ perror("send"); break; }
printf("发送:%s\n",buffer);
}
// 第6步:关闭socket,释放资源。
close(listenfd); close(clientfd);
}
TcpClient类
class CTcpClient
{
public:
int m_connfd; // 客户端的socket.
char m_ip[21]; // 服务端的ip地址。
int m_port; // 与服务端通讯的端口。
bool m_btimeout; // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
int m_buflen; // 调用Read方法后,接收到的报文的大小,单位:字节。
CTcpClient(); // 构造函数。
// 向服务端发起连接请求。
// ip:服务端的ip地址。
// port:服务端监听的端口。
// 返回值:true-成功;false-失败。
bool ConnectToServer(const char *ip,const int port);
// 接收服务端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool Read(char *buffer,const int itimeout=0);
// 向服务端发送数据。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool Write(const char *buffer,const int ibuflen=0);
// 断开与服务端的连接
void Close();
~CTcpClient(); // 析构函数自动关闭socket,释放资源。
};
----实现
CTcpClient::CTcpClient()
{
m_connfd=-1;
memset(m_ip,0,sizeof(m_ip));
m_port=0;
m_btimeout=false;
}
bool CTcpClient::ConnectToServer(const char *ip,const int port)
{
// 如果已连接到服务端,则断开,这种处理方法没有特别的原因,不要纠结。
if (m_connfd!=-1) { close(m_connfd); m_connfd=-1; }
// 忽略SIGPIPE信号,防止程序异常退出。
// 如果send到一个disconnected socket上,内核就会发出SIGPIPE信号。这个信号
// 的缺省处理方法是终止进程,大多数时候这都不是我们期望的。我们重新定义这
// 个信号的处理方法,大多数情况是直接屏蔽它。
signal(SIGPIPE,SIG_IGN);
STRCPY(m_ip,sizeof(m_ip),ip);
m_port=port;
struct hostent* h;
struct sockaddr_in servaddr;
if ( (m_connfd = socket(AF_INET,SOCK_STREAM,0) ) < 0) return false;
if ( !(h = gethostbyname(m_ip)) )
{
close(m_connfd); m_connfd=-1; return false;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(m_port); // 指定服务端的通讯端口
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
if (connect(m_connfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
close(m_connfd); m_connfd=-1; return false;
}
return true;
}
// 接收服务端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool CTcpClient::Read(char *buffer,const int itimeout)
{
if (m_connfd==-1) return false;
// 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
if (itimeout>0)
{
struct pollfd fds;
fds.fd=m_connfd;
fds.events=POLLIN;
int iret;
m_btimeout=false;
if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
{
if (iret==0) m_btimeout = true;
return false;
}
}
m_buflen = 0;
return (TcpRead(m_connfd,buffer,&m_buflen));
}
bool CTcpClient::Write(const char *buffer,const int ibuflen)
{
if (m_connfd==-1) return false;
int ilen=ibuflen;
if (ibuflen==0) ilen=strlen(buffer);
return(TcpWrite(m_connfd,buffer,ilen));
}
void CTcpClient::Close()
{
if (m_connfd > 0) close(m_connfd);
m_connfd=-1;
memset(m_ip,0,sizeof(m_ip));
m_port=0;
m_btimeout=false;
}
CTcpClient::~CTcpClient()
{
Close();
}
socket通讯的服务端类
{
private:
int m_socklen; // 结构体struct sockaddr_in的大小。
struct sockaddr_in m_clientaddr; // 客户端的地址信息。
struct sockaddr_in m_servaddr; // 服务端的地址信息。
public:
int m_listenfd; // 服务端用于监听的socket。
int m_connfd; // 客户端连接上来的socket。
bool m_btimeout; // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
int m_buflen; // 调用Read方法后,接收到的报文的大小,单位:字节。
CTcpServer(); // 构造函数。
// 服务端初始化。
// port:指定服务端用于监听的端口。
// 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
bool InitServer(const unsigned int port,const int backlog=5);
// 阻塞等待客户端的连接请求。
// 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
bool Accept();
// 获取客户端的ip地址。
// 返回值:客户端的ip地址,如"192.168.1.100"。
char *GetIP();
// 接收客户端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool Read(char *buffer,const int itimeout=0);
// 向客户端发送数据。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool Write(const char *buffer,const int ibuflen=0);
// 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
void CloseListen();
// 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
void CloseClient();
~CTcpServer(); // 析构函数自动关闭socket,释放资源。
};
-----实现
CTcpServer::CTcpServer()
{
m_listenfd=-1;
m_connfd=-1;
m_socklen=0;
m_btimeout=false;
}
bool CTcpServer::InitServer(const unsigned int port,const int backlog)
{
// 如果服务端的socket>0,关掉它,这种处理方法没有特别的原因,不要纠结。
if (m_listenfd > 0) { close(m_listenfd); m_listenfd=-1; }
if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false;
// 忽略SIGPIPE信号,防止程序异常退出。
signal(SIGPIPE,SIG_IGN);
// 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器,
// 否则bind()可能会不成功,报:Address already in use。
//char opt = 1; unsigned int len = sizeof(opt);
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
memset(&m_servaddr,0,sizeof(m_servaddr));
m_servaddr.sin_family = AF_INET;
m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
m_servaddr.sin_port = htons(port);
if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 )
{
CloseListen(); return false;
}
if (listen(m_listenfd,backlog) != 0 )
{
CloseListen(); return false;
}
return true;
}
bool CTcpServer::Accept()
{
if (m_listenfd==-1) return false;
m_socklen = sizeof(struct sockaddr_in);
if ((m_connfd=accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0)
return false;
return true;
}
char *CTcpServer::GetIP()
{
return(inet_ntoa(m_clientaddr.sin_addr));
}
bool CTcpServer::Read(char *buffer,const int itimeout)
{
if (m_connfd==-1) return false;
// 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
if (itimeout>0)
{
struct pollfd fds;
fds.fd=m_connfd;
fds.events=POLLIN;
m_btimeout=false;
int iret;
if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
{
if (iret==0) m_btimeout = true;
return false;
}
}
m_buflen = 0;
return(TcpRead(m_connfd,buffer,&m_buflen));
}
bool CTcpServer::Write(const char *buffer,const int ibuflen)
{
if (m_connfd==-1) return false;
int ilen = ibuflen;
if (ilen==0) ilen=strlen(buffer);
return(TcpWrite(m_connfd,buffer,ilen));
}
void CTcpServer::CloseListen()
{
if (m_listenfd > 0)
{
close(m_listenfd); m_listenfd=-1;
}
}
void CTcpServer::CloseClient()
{
if (m_connfd > 0)
{
close(m_connfd); m_connfd=-1;
}
}
CTcpServer::~CTcpServer()
{
CloseListen(); CloseClient();
}
接收socket的对端发送过来的数据。
bool TcpRead(const int sockfd,char *buffer,int *ibuflen,const int itimeout)
{
if (sockfd==-1) return false;
if (itimeout>0)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
}
if (itimeout==-1)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,0) <= 0 ) return false;
}
(*ibuflen) = 0;
if (Readn(sockfd,(char*)ibuflen,4) == false) return false;
(*ibuflen)=ntohl(*ibuflen);
if (Readn(sockfd,buffer,(*ibuflen)) == false) return false;
return true;
}
向socket的对端发送数据。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen填0或字符串的长度,
// 如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen)
{
if (sockfd==-1) return false;
int ilen=0; // 报文长度。
// 如果ibuflen==0,就认为需要发送的是字符串,报文长度为字符串的长度。
if (ibuflen==0) ilen=strlen(buffer);
else ilen=ibuflen;
int ilenn=htonl(ilen); // 把报文长度转换为网络字节序。
char TBuffer[ilen+4]; // 发送缓冲区。
memset(TBuffer,0,sizeof(TBuffer)); // 清区发送缓冲区。
memcpy(TBuffer,&ilenn,4); // 把报文长度拷贝到缓冲区。
memcpy(TBuffer+4,buffer,ilen); // 把报文内容拷贝到缓冲区。
// 发送缓冲区中的数据。
if (Writen(sockfd,TBuffer,ilen+4) == false) return false;
return true;
}
从已经准备好的socket中读取数据。
bool Readn(const int sockfd,char *buffer,const size_t n)
{
int nLeft=n;
int idx=0;
int nread;
while(nLeft > 0)
{
if ( (nread=recv(sockfd,buffer+idx,nLeft,0)) <= 0) return false;
idx=idx+nread;
nLeft=nLeft-nread;
}
return true;
}
向已经准备好的socket中写入数据。
bool Writen(const int sockfd,const char *buffer,const size_t n)
{
int nLeft=n;
int idx=0;
int nwritten;
while(nLeft > 0 )
{
if ( (nwritten=send(sockfd,buffer+idx,nLeft,0)) <= 0) return false;
nLeft=nLeft-nwritten;
idx=idx+nwritten;
}
return true;
}
采用TcpClient类实现socket通讯的客户端
#include "../_public.h"
int main(int argc,char *argv[])
{
if (argc!=3)
{
printf("Using:./demo07 ip port\nExample:./demo07 127.0.0.1 5005\n\n"); return -1;
}
CTcpClient TcpClient;
if (TcpClient.ConnectToServer(argv[1],atoi(argv[2]))==false)
{
printf("TcpClient.ConnectToServer(%s,%s) failed.\n",argv[1],argv[2]); return -1;
}
char buffer[102400];
for (int ii=0;ii<100000;ii++)
{
SPRINTF(buffer,sizeof(buffer),"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
if (TcpClient.Write(buffer)==false) break;
printf("发送:%s\n",buffer);
memset(buffer,0,sizeof(buffer));
if (TcpClient.Read(buffer)==false) break;
printf("接收:%s\n",buffer);
sleep(1);
}
}
采用TcpServer类实现socket通讯的服务端
#include "../_public.h"
int main(int argc,char *argv[])
{
if (argc!=2)
{
printf("Using:./demo08 port\nExample:./demo08 5005\n\n"); return -1;
}
CTcpServer TcpServer;
if (TcpServer.InitServer(atoi(argv[1]))==false)
{
printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
}
if (TcpServer.Accept()==false)
{
printf("TcpServer.Accept() failed.\n"); return -1;
}
printf("客户端(%s)已连接。\n",TcpServer.GetIP());
char buffer[102400];
while (1)
{
memset(buffer,0,sizeof(buffer));
if (TcpServer.Read(buffer)==false) break;
printf("接收:%s\n",buffer);
strcpy(buffer,"ok");
if (TcpServer.Write(buffer)==false) break;
printf("发送:%s\n",buffer);
}
}
CTcpServer类实现socket通讯多进程的服务端
- 1)在多进程的服务程序中,如果杀掉一个子进程,和这个子进程通讯的客户端会断开,但是,不
会影响其它的子进程和客户端,也不会影响父进程。
- 2)如果杀掉父进程,不会影响正在通讯中的子进程,但是,新的客户端无法建立连接。
- 3)如果用killall+程序名,可以杀掉父进程和全部的子进程。
- 多进程网络服务端程序退出的三种情况:
- 1)如果是子进程收到退出信号,该子进程断开与客户端连接的socket,然后退出。
- 2)如果是父进程收到退出信号,父进程先关闭监听的socket,然后向全部的子进程发出退出信号。
- 3)如果父子进程都收到退出信号,本质上与第2种情况相同。
#include "../_public.h"
CLogFile logfile;
CTcpServer TcpServer;
void FathEXIT(int sig);
void ChldEXIT(int sig);
int main(int argc,char *argv[])
{
if (argc!=3)
{
printf("Using:./demo10 port logfile\nExample:./demo10 5005 /tmp/demo10.log\n\n"); return -1;
}
CloseIOAndSignal(); signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);
if (logfile.Open(argv[2],"a+")==false) { printf("logfile.Open(%s) failed.\n",argv[2]); return -1; }
if (TcpServer.InitServer(atoi(argv[1]))==false)
{
logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
}
while (true)
{
if (TcpServer.Accept()==false)
{
logfile.Write("TcpServer.Accept() failed.\n"); FathEXIT(-1);
}
logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());
if (fork()>0) { TcpServer.CloseClient(); continue; }
signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);
TcpServer.CloseListen();
char buffer[102400];
while (1)
{
memset(buffer,0,sizeof(buffer));
if (TcpServer.Read(buffer)==false) break;
logfile.Write("接收:%s\n",buffer);
strcpy(buffer,"ok");
if (TcpServer.Write(buffer)==false) break;
logfile.Write("发送:%s\n",buffer);
}
ChldEXIT(0);
}
}
void FathEXIT(int sig)
{
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
logfile.Write("父进程退出,sig=%d。\n",sig);
TcpServer.CloseListen();
kill(0,15);
exit(0);
}
void ChldEXIT(int sig)
{
signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
logfile.Write("子进程退出,sig=%d。\n",sig);
TcpServer.CloseClient();
exit(0);
}