#ifdef WIN32
#pragma once
#ifdef XSOCKET_EXPORTS
#define XSOCKET_API __declspec(dllexport)
#else
#define XSOCKET_API __declspec(dllimport)
#endif
#else
#define XSOCKET_API
#endif // WIN32
//tcp封装 在windows下封装成dll 在linux下封装成.so
#include<string>
class XSOCKET_API XTcp
{
public:
XTcp();
int CreateSocket();
bool Bind(unsigned short port);
XTcp Accept();
bool Connect(const char*ip ,unsigned short port,int timeoutms=1000);
//设置阻塞模式 利用跨平台select多路复用实现超时功能
bool SetBlock(bool isblock);
int Recv(char*buf,int bufsize);
int Send(const char*buf, int sendSize);
void Close();
virtual~XTcp();
int sock = 0;
unsigned short port = 0;
char ip[16] = "";
};
#include "XTcp.h"
#include<string.h>
#ifdef WIN32
#include<windows.h>
#else
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include<fcntl.h>
#define closesocket close
#endif
XTcp::XTcp()
{
//linux中不需要加载网络库
#ifdef WIN32
static bool first = true;
if (first == true)
{
WSADATA ws;
WSAStartup(MAKEWORD(2, 2), &ws);
first = false;
}
#endif
}
int XTcp::CreateSocket()
{
sock = socket(AF_INET, SOCK_STREAM, 0);
printf("[%d]\n", sock);
return sock;
}
bool XTcp::Bind(unsigned short port)
{
sockaddr_in addr;
addr.sin_addr.s_addr = htonl(0); //任意IP地址发过来都接收
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (::bind(sock, (const sockaddr*)&addr, sizeof(addr)) != 0)
{
printf("bind port %d failed\n", port);
return false;
}
printf("bind port %d success\n", port);
listen(sock, 10); //该套接字使用的最大队列长度,代表最多同事监听10个客户端的连接
return true;
}
/*在服务端掉这个函数返回一个新的对象难过*/
XTcp XTcp::Accept()
{
XTcp tcp;
sockaddr_in caddr;
#ifdef WIN32
int addrLen = sizeof(caddr);
#else
socklen_t addrLen = sizeof(caddr);
#endif //
tcp.sock = accept(sock, (sockaddr*)&caddr, &addrLen);
//int client = accept(sock, 0, 0); //不关注客户端的信息
printf("accept client %d\n", sock);
char*ip= inet_ntoa(caddr.sin_addr);
strcpy(tcp.ip, ip);
tcp.port = ntohs(caddr.sin_port);
printf(" client ip %s port %d\n", ip, tcp.port);
return tcp;
}
bool XTcp::Connect(const char*ip, unsigned short port, int timeoutms)
{
if (sock <= 0) CreateSocket();
sockaddr_in caddr;
caddr.sin_family = inet_addr(ip);
caddr.sin_port = htons(port);
caddr.sin_family = AF_INET;
#ifdef WIN32
int addrLen = sizeof(caddr);
#else
socklen_t addrLen = sizeof(caddr);
#endif //
SetBlock(false); //先设置成给非阻塞模式 利用timeoutms精确控制超时时间
fd_set set; //该对象用于存放多个文件句柄的状态,文件属组
if (connect(sock, (sockaddr*)(&caddr), addrLen) != 0)
{
//连接失败 利用select精确的控制超时的时间 但前提是设置sock为非阻塞模式
FD_ZERO(&set);
FD_SET(sock,&set); //将sock加入该文件列表
timeval tm; //超时时间select实际也会一个阻塞函数,最后一个函数超时返回时间
tm.tv_usec = timeoutms*1000;
tm.tv_sec = 0;
//用select来监听文件数据是否发生了可读可写
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;
}
printf("connect %s:%d success!\n", ip, port);
SetBlock(true); //设置回来 不然recv不阻塞
return true;
}
//超时是通过设置sock进行的
bool XTcp::SetBlock(bool isblock)
{
#ifdef WIN32
if (sock <= 0)return false;
unsigned long ul = 0;
//非阻塞模式 ul=1
if (!isblock)ul = 1;
ioctlsocket(sock, FIONBIO, &ul);
#else //linux......
//获取原有sock的属性 返回值为返回的结果
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;
}
int XTcp::Recv(char * buf, int bufsize)
{
//一次接收的最大值
int len = recv(sock, buf, bufsize, 0);
return len;
}
int XTcp::Send(const char * buf, int sendSize)
{
int haveSend = 0;
while (haveSend != sendSize)
{
int sendLen = send(sock,buf + haveSend, sendSize, 0);
haveSend += sendLen;
sendSize -= haveSend;
if (sendLen <= 0 )break;
}
return haveSend;
}
void XTcp::Close()
{
closesocket(sock);
}
XTcp::~XTcp()
{
}
调用过程服务器端:
#include"XTcp.h"
#include<stdio.h>
#include <stdlib.h>
#include<thread>
#include<string.h>
using namespace std;
//三次握手是在客户端连接,服务端在listen后 就已经完成,跟accpet没有关系
//使用ulimit -n 查看linux中最大可以创建的socket数量
class TcpThread
{
public:
void main()
{
char buf[1024] = { 0 };
for (;;)
{
int len = client.Recv(buf, sizeof(buf) - 1);
buf[len] = '\0';
printf("recv %s\n", buf);
if (strstr(buf, "quit") != 0)
{
char sendStr[] = "quit success !\n";
int sendLen = client.Send(sendStr, strlen(sendStr) + 1);
break;
}
int sendLen = client.Send("ok\n", 4);
}
client.Close();
delete this; //自己清理自己,确保这个对象是new出来的
}
XTcp client;
};
int main(int argc, char*argv[])
{
XTcp tcp;
int sock = tcp.CreateSocket();
unsigned short port = 8080;
//port 命令行参数传入
if (argc > 1)
port = atoi(argv[1]);
tcp.Bind(port);
for (;;)
{
XTcp clientTcp = tcp.Accept();
TcpThread *th = new TcpThread;
th->client = clientTcp;
thread sth(&TcpThread::main, th);
sth.detach();
}
tcp.Close();
return 0;
}
调用过程客户端:
#include"XTcp.h"
int main()
{
XTcp client;
client.CreateSocket();
//client.SetBlock(false); //设置非阻塞
//实际是阻塞的 IP错误超时比较慢 端口错误超时快一点
bool status = client.Connect("192.168.211.128",8080);
client.Send("hello", 5);
char buf[1024] = { 0 };
client.Recv(buf, sizeof(buf));
printf("recv:%s", buf);
return 0;
}