欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

简单socket编程服务端和客户端流程以及TCP类的封装

程序员文章站 2022-06-06 16:06:13
...

TCP服务端流程:

创建socket-》bind-》listen-》accept-》recv-》send-》close
第一步创建的socket用于监听链接请求,accept也会返回一个新的socket用于recv和send

创建 socket:

int sock = socket(AF_INET, SOCK_STREAM, 0);

bind:

sockaddr_in saddr;
saddr.sin_family = AF_INET; //ipv4
saddr.sin_port = port; //端口port
saddr.sin_addr.s_addr = htonl(0); //传0绑定本机ip
if(::bind(sock, (sockaddr*)&saddr, sizeof(saddr))!=0){//::表示使用全局函数
	return -2;
}

listen:

listen(sock,10); //监听等待队列上限10

accept:

sockaddr_in caddr; //用来保存连接请求的客户端ip和port信息
socklen_t len = sizeof(caddr);
int client = accept(sock, (sockaddr*)&caddr, &len);//返回一个新的socket用于接受发送数据

recv:

char buf[10240] = {0};
int recvlen = recv(client, buf, sizeof(buf)-1, 0);

send:

int sendlen = send(client, buf, sizeof(buf), 0);

recv和send都是对accept返回的socket操作,可以放到线程函数中去,使用linux和windows通用的c++11 线程库,可以同时响应多个请求

#ifndef WIN32
#define closesocket close
#endif
class TcpThread
{
public:
	void Main()
	{
		char buf[1024] = {0};
		for(;;)
		{
			int recvlen = recv(client, buf, sizeof(buf)-1, 0);
			if(recvlen <= 0 || strstr(buf, "quit")!=NULL)
			{
				char re[] = "quit succeed!\n";
				send(client, re, strlen(re)+1, 0);
				break;
			}
			buf[recvlen] = '\0';
		}
		closesocket(client);
		delete this;
	}
	int client = 0;
}
for(;;)
{
	sockaddr_in caddr;
	socklen_t len = sizeof(caddr);
	int client = accept(sock, (sockaddr*)&caddr, &len);
	if(client <= 0) break;
	TcpThread *th = new TcpThread();
	th->client = client;
	thread sth(&TcpThread::Main, th);
	sth.detach();
}

如果是只在linux下,则可以使用更加高效的epoll多路复用:
epoll多路复用适合保持大量并发连接而活跃的连接不多的情况下,但是不能用于windows

int epfd = epoll_create(256);
//注册epoll事件
struct epoll_event ev;
ev.data.fd = server.sock;
//数据接入事件|边缘检测
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,server.sock,&ev);

struct epoll_event event[20];
char buf[1024] = {0};
const char *msg = "HTTP/1.1 200 OK\r\nContent-Length: 1\r\n\r\nX";
int size = strlen(msg);
	server.SetBlock(false);
	for (;;)
	{
		//检测事件
		int count = epoll_wait(epfd,event,20,500);
		if(count <=0) continue;
		for(int i = 0; i < count; i++)
		{
			//连接事件发生
			if(event[i].data.fd == server.sock)
			{
				for(;;)
				{
					XTcp client = server.Accept();
					if(client.sock<=0) break;
					
					//新注册客户端事件
					ev.data.fd = client.sock;
					ev.events=EPOLLIN|EPOLLET;
					epoll_ctl(epfd,EPOLL_CTL_ADD,client.sock,&ev);
				}
			}
			else
			{
				XTcp client ;
				client.sock = event[i].data.fd;
				client.Recv(buf,1024);
				client.Send(msg,size);
				//客户端处理完毕,清理事件
				epoll_ctl(epfd,EPOLL_CTL_DEL,client.sock,&ev);
				client.Close();
			}
		}
	}

close:

close(client); //windows 下是 closesocket(client);
close(sock);

TCP客户端流程:
创建socket-》connect-》recv-》send-》close
connect:

connect(sock,(sockaddr*)&saddr, 0);

connect默认是阻塞的,可以设置为非阻塞, 然后用select设置超时
select在linux和windows下通用的:

#ifdef WIN32 //windows下
	unsigned long ul = 1;
	ioctlsocket(sock,FIONBIO,&ul); //把socket设置为非阻塞
#else //linux下
	int flags = fcntl(sock, F_GETFL, 0);
	flags = flags | O_NONBLOCK;
	fcntl(sock, F_SETFL, flags); //把socket设置为非阻塞
#endif
	fd_set set;
	if(connect(sock,(sockaddr*)&saddr, sizeof(saddr))!=0)
	{
		FD_ZERO(&set); //清空set
		FD_SET(sock,&set);
		timeval tm;
		tm.tv_sec = 0;
		tm.tv_usec = 300*1000; //300ms
		if(select(sock+1, 0, &set, 0, tm) <= 0) return false; //设置超时时间为300ms
	}

TCP类的封装:

封装可以隐藏系统函数调用和多余参数,简化调用的代码并实现代码复用
动态库的生成和使用

XTcp.h:

#ifndef XTCP_H
#define XTCP_H

#ifdef WIN32
#ifdef XSOCKET_EXPORTS
#define XSOCKET_API __declspec(dllexport)
#else
#define XSOCKET_API __declspec(dllimport)
#endif
#else
#define XSOCKET_API
#endif



#include <string>
XSOCKET_API std::string GetIpByHost(std::string host);

class XSOCKET_API XTcp
{
public:
	int CreateSocket();
	bool Bind(unsigned short port);
	XTcp Accept();
	void Close();
	int Recv(char *buf,int bufsize);
	int Send(const char *buf,int sendsize);
	bool Connect(const char *ip,unsigned short port,int timeoutms=1000);
	bool SetBlock(bool isblock);
	XTcp();
	virtual ~XTcp();
	int sock = 0;
	unsigned short port = 0;
	char ip[16];

};

#endif

XTcp.cpp:

#include "XTcp.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
#include <windows.h>
#define socklen_t int
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define  closesocket close
#endif

#include <thread>
using namespace std;
XSOCKET_API std::string GetIpByHost(std::string host)
{
	hostent *hptr = gethostbyname(host.c_str());
	if (!hptr)return "";
	in_addr addr;
	addr.s_addr = *(unsigned long *)hptr->h_addr;
	return inet_ntoa(addr);
}
XTcp::XTcp()
{
#ifdef WIN32
	static bool first = true;
	if (first)
	{
		first = false;
		WSADATA ws;
		WSAStartup(MAKEWORD(2, 2), &ws);
	}
#endif
}
int XTcp::CreateSocket()
{
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock == -1)
	{
		printf("create socket failed!\n");
	}
	return sock;
}
bool XTcp::Bind(unsigned short port)
{
	if (sock <= 0)
		CreateSocket();
	sockaddr_in  saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	saddr.sin_addr.s_addr = htonl(0);
	if (::bind(sock, (sockaddr*)&saddr, sizeof(saddr)) != 0)
	{
		printf("bind port %d failed!\n", port);
		return false;
	}
	printf("bind port %d success!\n", port);
	listen(sock, 1000);
	return true;
}
int XTcp::Recv(char *buf, int bufsize)
{
	return recv(sock, buf, bufsize, 0);
}
int XTcp::Send(const char *buf, int size)
{
	int s = 0;
	while (s != size)
	{
		int len = send(sock, buf + s, size - s, 0);
		if (len <= 0)break;
		s += len;
	}
	return s;
}
void XTcp::Close()
{
	if (sock <= 0) return;
	closesocket(sock);
	sock = 0;
}
XTcp XTcp::Accept()
{
	XTcp tcp;
	sockaddr_in caddr;
	socklen_t len = sizeof(caddr);
	int client = accept(sock, (sockaddr*)&caddr, &len);
	if (client <= 0) return tcp;
	//printf("accept client %d\n", client);
	printf(".");
	char *ip = inet_ntoa(caddr.sin_addr);
	strcpy(tcp.ip, ip);
	tcp.port = ntohs(caddr.sin_port);
	tcp.sock = client;
	//printf("client ip is %s,port is %d\n", tcp.ip, tcp.port);
	return tcp;
}

bool XTcp::Connect(const char *ip,unsigned short port,int timeoutms)
{
	if(sock <=0 ) CreateSocket();
	sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(port);
	saddr.sin_addr.s_addr = inet_addr(ip);
	SetBlock(false);
	fd_set set;
	if(connect(sock,(sockaddr*)&saddr,sizeof(saddr))!=0)
	{
		FD_ZERO(&set);
		FD_SET(sock,&set);
		timeval tm;
		tm.tv_sec = 0;
		tm.tv_usec = timeoutms*1000;
		if(select(sock+1,0,&set,0,&tm) <= 0)
		{
			printf("connect timeout or error!\n");
			printf("connect %s:%d failed!:%s\n",ip,port,strerror(errno));
			return false;
		}
	}
	SetBlock(true);
	printf("connect %s:%d success!\n",ip,port);
	return true;
}
XTcp::~XTcp()
{
}
bool XTcp::SetBlock(bool isblock)
{
	if(sock<=0) return false;
	#ifdef WIN32
		unsigned long ul = 0;
		if(!isblock) ul = 1;
		ioctlsocket(sock,FIONBIO,&ul);
	#else
		int flags = fcntl(sock,F_GETFL,0);
		if(flags<0)
			return false;
		if(isblock)
		{
			flags = flags&~O_NONBLOCK;
		}
		else
		{
			flags = flags|O_NONBLOCK;
		}
		if(fcntl(sock,F_SETFL,flags)!=0)
			return false;
	#endif
	return true;
}