Windows Linux 跨平台简单tcp实现 基于设置socket非阻塞,select连接超时控制。

4 篇文章 1 订阅
3 篇文章 0 订阅


#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;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值