C++ socket 聊天实战

功能

使用了C++ socket,thread等方面的知识,支持多机聊天。

实现原理

本来是C/S架构,想通过一个服务器端进行信息转发,后来发现自己写一个服务器实在不太现实。所以把发送和接收的功能打包进一套程序,多机通过局域网内互联,实现聊天功能。

架构

main
{
    init socket
    recv info//接收消息
    while true:     
        if want to chat:
            type 'c'
            chat//发送消息
        if want to exit:
            type 'esc'
            break;
}

Socket

接收消息

WSADATA wsd2;
//接收消息服务器套接字
SOCKET Sserver;
//接收消息服务器套接字地址
SOCKADDR_IN addrServ;
//接收消息发送方远程客户端套接字
SOCKET Sclient;
//接收消息发送方远程客户端套接字地址
sockaddr_in addrClient;
//接收消息发送方远程客户端套接字地址长度
int addrClientLen;

//初始化套结字动态库  
if (WSAStartup(MAKEWORD(2, 2), &wsd2) != 0)
{
    cout << "WSAStartup failed!" << endl;
    return 1;
}
cout << "Socket Init..." << endl;
//创建套接字
Sserver = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//TCP协议
if (Sserver == INVALID_SOCKET)
{
    cout << "Failed Socket" << endl;
    WSACleanup();
    return -1;
}
cout << "Input Your Port" << endl;
int port;
cin >> port;
//设置服务器套接字
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons((short)port);
//addrServ.sin_addr.S_un.S_addr = INADDR_ANY;//0.0.0.0本机ip地址
addrServ.sin_addr.s_addr = inet_addr("127.0.0.1");//"127.0.0.1"
cout << "Service Socket Done..." << endl;
int addrServLen = sizeof(addrServ);
//绑定套接字
int ret = ::bind(Sserver, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));//socket里的bind和std::bind重复出错

if (ret == SOCKET_ERROR)
{
    cout << "bind failed" << endl;
    closesocket(Sserver);
    WSACleanup();
    return -1;
}
cout << "Bind Done..." << endl;
ret = listen(Sserver, 10);//最大支持十个客户端连接
if (ret == SOCKET_ERROR)
{
    cout << "listen failed" << endl;
    closesocket(Sserver);
    WSACleanup();
    return -1;
}
cout << "Listen Done..." << endl;
addrClientLen = sizeof(addrClient);
// ready
Sleep(1000);
cout << "Open Service And Recv..." << endl;
while (true)
{
    cout << "Waiting Client..." << endl;
    int ch;
    if (_kbhit())
    {
        ch = _getch();
        cout << "You just type" << ch << endl;
        if (ch == 113)//q
        {
            return 0;
        }
    }
    //接收请求
    Sclient = accept(Sserver, (sockaddr FAR*)&addrClient, &addrClientLen);
    if (Sclient == INVALID_SOCKET)
    {
        cout << "failed socket" << endl;
        WSACleanup();
        return -1;
    }

    //接收数据
    const int BUFF_SIZE = 1024;
    char buf[BUFF_SIZE];
    char *reply = "Done!";
    while (true)
    {
        ZeroMemory(buf, BUFF_SIZE);//清空置零
        int ret = recv(Sclient, buf, BUFF_SIZE, 0);

        if (ret == SOCKET_ERROR)
        {
            cout << "recv failed" << endl;
            break;
            //return -1;
        }
        getpeername(Sclient, (struct sockaddr *)&addrClient, &addrClientLen); //查看链接对端的地址
        cout << "\n" << endl;
        //cout << "Friend from " << inet_ntoa(addrClient.sin_addr) << ":" << ntohs(addrClient.sin_port) << ":" << buf << endl;
        cout << "Friend:"<< buf << endl;
        getsockname(Sserver, (struct sockaddr *)&addrServ, &addrServLen);//查看链接本端的地址
        cout << "\n" << endl;
        //cout << "You are" << inet_ntoa(addrServ.sin_addr) << ":" << ntohs(addrServ.sin_port) << endl;
        //cout << "You:" << reply << endl;
        send(Sclient, reply, strlen(reply), 0);
    }
}

发送消息

发送消息主要需要设置好对方的地址和端口

//初始化套接字库
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
    cout << "WSAStartup failed" << endl;
    return -1;
}
cout << "Chat Socket Init..." << endl;
Sremotehost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (Sremotehost == INVALID_SOCKET)
{
    cout << "Chat Socket Failed" << endl;
    WSACleanup();
    return -1;
}
cout << "Service Socket Done..." << endl;
addrRemoteServ.sin_family = AF_INET;
addrRemoteServ.sin_port = htons((short)atoi(port));//4999atoi(port)
addrRemoteServ.sin_addr.s_addr = inet_addr(ip);//"127.0.0.1"
addrRemoteServLen = sizeof(addrRemoteServ);

//连接服务器
int ret = connect(Sremotehost, (LPSOCKADDR)&addrRemoteServ, addrRemoteServLen);
if (ret == SOCKET_ERROR)
{
    cout << "Connect Failed" << endl;
    cout << ::WSAGetLastError() << endl;
    closesocket(Sremotehost);
    WSACleanup();
    return -1;
}
cout << "Connect Done..." << endl;
return 0;

小结

C++ windows下使用socket
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib") //加载动态库
getpeername

当链接建立后,获得链接对端地址

//Socket,sockaddr_in,int Len = sizeof(sockaddr)
getpeername(Sclient, (struct sockaddr *)&addrClient, &addrClientLen); //获取connfd表示的连接上的对端地址
cout << "Friend from " << inet_ntoa(addrClient.sin_addr) << ":" << ntohs(addrClient.sin_port) << ":" << buf << endl;
getsockname

当链接建立后,获得本端地址

getsockname(Sserver, (struct sockaddr *)&addrServ, &addrServLen);
cout << "You are" << inet_ntoa(addrServ.sin_addr) << ":" << ntohs(addrServ.sin_port) << endl;
Socket bind函数与std bind函数重名

C++ 11后std中添加了bind函数,当使用using namespace std的时候会产生重名错误,可以使用::bind来使用socket中的bind。

WSAStartup

初始化套接字库,其只初始化一次,如果多次调用,会在计数器上加一,当调用结束使用WSACleanup()时,调用几次需要Clean几次,每次Clean会使计数器减一,减到0时释放资源。

Socket过程中的错误

可以通过 WSAGetLastError 函数查看具体错误,返回值为int型,其对应的错误在winsock包里。
cout << ::WSAGetLastError() << endl;
如果需要查询可以在包中搜索 10000,因为错误代码是从10000开始的:

#define WSABASEERR              10000

/*
 * Windows Sockets definitions of regular Microsoft C error constants
 */
#define WSAEINTR                (WSABASEERR+4)
#define WSAEBADF                (WSABASEERR+9)
#define WSAEACCES               (WSABASEERR+13)
#define WSAEFAULT               (WSABASEERR+14)
#define WSAEINVAL               (WSABASEERR+22)
#define WSAEMFILE               (WSABASEERR+24)

/*
 * Windows Sockets definitions of regular Berkeley error constants
 */
#define WSAEWOULDBLOCK          (WSABASEERR+35)
#define WSAEINPROGRESS          (WSABASEERR+36)
#define WSAEALREADY             (WSABASEERR+37)
#define WSAENOTSOCK             (WSABASEERR+38)
#define WSAEDESTADDRREQ         (WSABASEERR+39)
#define WSAEMSGSIZE             (WSABASEERR+40)
#define WSAEPROTOTYPE           (WSABASEERR+41)
#define WSAENOPROTOOPT          (WSABASEERR+42)
#define WSAEPROTONOSUPPORT      (WSABASEERR+43)
#define WSAESOCKTNOSUPPORT      (WSABASEERR+44)
#define WSAEOPNOTSUPP           (WSABASEERR+45)
#define WSAEPFNOSUPPORT         (WSABASEERR+46)
#define WSAEAFNOSUPPORT         (WSABASEERR+47)
#define WSAEADDRINUSE           (WSABASEERR+48)
#define WSAEADDRNOTAVAIL        (WSABASEERR+49)
#define WSAENETDOWN             (WSABASEERR+50)
#define WSAENETUNREACH          (WSABASEERR+51)
#define WSAENETRESET            (WSABASEERR+52)
#define WSAECONNABORTED         (WSABASEERR+53)
#define WSAECONNRESET           (WSABASEERR+54)
#define WSAENOBUFS              (WSABASEERR+55)
#define WSAEISCONN              (WSABASEERR+56)
#define WSAENOTCONN             (WSABASEERR+57)
#define WSAESHUTDOWN            (WSABASEERR+58)
#define WSAETOOMANYREFS         (WSABASEERR+59)
#define WSAETIMEDOUT            (WSABASEERR+60)
#define WSAECONNREFUSED         (WSABASEERR+61)
#define WSAELOOP                (WSABASEERR+62)
#define WSAENAMETOOLONG         (WSABASEERR+63)
#define WSAEHOSTDOWN            (WSABASEERR+64)
#define WSAEHOSTUNREACH         (WSABASEERR+65)
#define WSAENOTEMPTY            (WSABASEERR+66)
#define WSAEPROCLIM             (WSABASEERR+67)
#define WSAEUSERS               (WSABASEERR+68)
#define WSAEDQUOT               (WSABASEERR+69)
#define WSAESTALE               (WSABASEERR+70)
#define WSAEREMOTE              (WSABASEERR+71)

/*
 * Extended Windows Sockets error constant definitions
 */
#define WSASYSNOTREADY          (WSABASEERR+91)
#define WSAVERNOTSUPPORTED      (WSABASEERR+92)
#define WSANOTINITIALISED       (WSABASEERR+93)
#define WSAEDISCON              (WSABASEERR+101)
#define WSAENOMORE              (WSABASEERR+102)
#define WSAECANCELLED           (WSABASEERR+103)
#define WSAEINVALIDPROCTABLE    (WSABASEERR+104)
#define WSAEINVALIDPROVIDER     (WSABASEERR+105)
#define WSAEPROVIDERFAILEDINIT  (WSABASEERR+106)
#define WSASYSCALLFAILURE       (WSABASEERR+107)
#define WSASERVICE_NOT_FOUND    (WSABASEERR+108)
#define WSATYPE_NOT_FOUND       (WSABASEERR+109)
#define WSA_E_NO_MORE           (WSABASEERR+110)
#define WSA_E_CANCELLED         (WSABASEERR+111)
#define WSAEREFUSED             (WSABASEERR+112)

/*
 * Error return codes from gethostbyname() and gethostbyaddr()
 * (when using the resolver). Note that these errors are
 * retrieved via WSAGetLastError() and must therefore follow
 * the rules for avoiding clashes with error numbers from
 * specific implementations or language run-time systems.
 * For this reason the codes are based at WSABASEERR+1001.
 * Note also that [WSA]NO_ADDRESS is defined only for
 * compatibility purposes.
 */

/* Authoritative Answer: Host not found */
#define WSAHOST_NOT_FOUND       (WSABASEERR+1001)

/* Non-Authoritative: Host not found, or SERVERFAIL */
#define WSATRY_AGAIN            (WSABASEERR+1002)

/* Non-recoverable errors, FORMERR, REFUSED, NOTIMP */
#define WSANO_RECOVERY          (WSABASEERR+1003)

/* Valid name, no data record of requested type */
#define WSANO_DATA              (WSABASEERR+1004)

/*
 * Define QOS related error return codes
 *
 */
#define  WSA_QOS_RECEIVERS               (WSABASEERR + 1005)
         /* at least one Reserve has arrived */
#define  WSA_QOS_SENDERS                 (WSABASEERR + 1006)
         /* at least one Path has arrived */
#define  WSA_QOS_NO_SENDERS              (WSABASEERR + 1007)
         /* there are no senders */
#define  WSA_QOS_NO_RECEIVERS            (WSABASEERR + 1008)
         /* there are no receivers */
#define  WSA_QOS_REQUEST_CONFIRMED       (WSABASEERR + 1009)
         /* Reserve has been confirmed */
#define  WSA_QOS_ADMISSION_FAILURE       (WSABASEERR + 1010)
         /* error due to lack of resources */
#define  WSA_QOS_POLICY_FAILURE          (WSABASEERR + 1011)
         /* rejected for administrative reasons - bad credentials */
#define  WSA_QOS_BAD_STYLE               (WSABASEERR + 1012)
         /* unknown or conflicting style */
#define  WSA_QOS_BAD_OBJECT              (WSABASEERR + 1013)
         /* problem with some part of the filterspec or providerspecific
          * buffer in general */
#define  WSA_QOS_TRAFFIC_CTRL_ERROR      (WSABASEERR + 1014)
         /* problem with some part of the flowspec */
#define  WSA_QOS_GENERIC_ERROR           (WSABASEERR + 1015)
         /* general error */
#define  WSA_QOS_ESERVICETYPE            (WSABASEERR + 1016)
         /* invalid service type in flowspec */
#define  WSA_QOS_EFLOWSPEC               (WSABASEERR + 1017)
         /* invalid flowspec */
#define  WSA_QOS_EPROVSPECBUF            (WSABASEERR + 1018)
         /* invalid provider specific buffer */
#define  WSA_QOS_EFILTERSTYLE            (WSABASEERR + 1019)
         /* invalid filter style */
#define  WSA_QOS_EFILTERTYPE             (WSABASEERR + 1020)
         /* invalid filter type */
#define  WSA_QOS_EFILTERCOUNT            (WSABASEERR + 1021)
         /* incorrect number of filters */
#define  WSA_QOS_EOBJLENGTH              (WSABASEERR + 1022)
         /* invalid object length */
#define  WSA_QOS_EFLOWCOUNT              (WSABASEERR + 1023)
         /* incorrect number of flows */
#define  WSA_QOS_EUNKOWNPSOBJ            (WSABASEERR + 1024)
         /* unknown object in provider specific buffer */
#define  WSA_QOS_EPOLICYOBJ              (WSABASEERR + 1025)
         /* invalid policy object in provider specific buffer */
#define  WSA_QOS_EFLOWDESC               (WSABASEERR + 1026)
         /* invalid flow descriptor in the list */
#define  WSA_QOS_EPSFLOWSPEC             (WSABASEERR + 1027)
         /* inconsistent flow spec in provider specific buffer */
#define  WSA_QOS_EPSFILTERSPEC           (WSABASEERR + 1028)
         /* invalid filter spec in provider specific buffer */
#define  WSA_QOS_ESDMODEOBJ              (WSABASEERR + 1029)
         /* invalid shape discard mode object in provider specific buffer */
#define  WSA_QOS_ESHAPERATEOBJ           (WSABASEERR + 1030)
         /* invalid shaping rate object in provider specific buffer */
#define  WSA_QOS_RESERVED_PETYPE         (WSABASEERR + 1031)
         /* reserved policy element in provider specific buffer */

thread

C++ thread 是std命名空间下支持的多线程。可以在支持C++11的编译器中使用,不论环境。

#include<thread>
using namespace std;

int func1()
{}

int main()
{
    //启动线程函数
    thread t1(func1);
    {
        something
        other thread
    }
    //线程同步,保证子线程结束前主线程不结束
    t1.join();
}

windows检测按键

#include<conio.h>
int ch;
if (_kbhit())//是否有按键事件
{
    ch = _getch();//获取按键键值
    cout << "You just type" << ch << endl;
    if (ch == 27)//Esc
    {
        cout << "It's time to see goodbye..." << endl;
        break;
    }           
    if (ch == 99)//c
    {
        cout << "You just want to talk" << endl;
        thread talk(chat);
        talk.join();
        //chat();
    }
    cout << "Please type your order.\nYou can use Esc for exit or use 'c' for chat.\n" << endl;
}

codeblock 使用socket

需要添加库函数setting->compiler->Link Settings->Link Libraries->add添加libws2_32.a和linwsock32.a路径(C:\Program Files\CodeBlocks\MinGW\lib)

源码

https://download.csdn.net/download/san_junipero/10397080

可改进之处

服务器线程池并发处理请求,防止阻塞

猜你喜欢

转载自blog.csdn.net/san_junipero/article/details/80223263