[多線程通訊程式]C++基於Socket的一款多人線上通訊程式

來源:互聯網
上載者:User

標籤:基於   發送   local   attr   char   dev   緩衝   code   線上   

廢話不多說,先。

進入正題:最近閑著無聊,想起來在初二時用VB寫的一個區域網路多人聊天室。當時用的是Winsock,然後寫出來給同學上資訊課用,其實也沒啥用啊。

今天下午突發奇想,打算用C++實現這一功能。去百度了一下相關資料,才發現C++的socket是真的麻煩。。。。。。(或許是我太菜了)於是於是於是,我很認真地開始學習(Copy)。找了好久都沒找到符合我意思的模板,而且突然發現如果要多人同時線上聊天的話,好像還要多線程來著,,???,,。於是我開始很認真的學習(別說了,是真的開始學習)。具體的socket原理請點我

經過了一個下午的學習(Copy),我終於大致清楚了socket的工作原理,以及整個通訊程式的大致思路。(引用一下某大佬部落格的原文ε=ε=ε=┏(゜ロ゜;)┛)

我們編程應首先確定你需要的功能即你需要實現的“方法”,如socket通訊端中對連接埠號碼的設定,對IP地址的設定,對阻塞模式的設定,對其他用戶端的監聽和接收,以及各種錯誤資訊的設定和反饋。而在網路編程多人聊天室的編碼過程中,我經過多次的嘗試和總結以後,理出了一個大概的思路——即首先封裝socket通訊端,然後按邏輯封裝其他的功能類。

不過我還是搞了好久。。。。。。我還是來總結一下我遇到的問題吧。

首先是編譯的問題。雖然我之前用的都是VC,但是這次我是用Dev-c++開的工程。於是在編譯的時候遇到了幾個神奇的問題。後來瀏覽了幾個大佬的部落格,發現Dev-c++要自己配置編譯參數。總結一下,如果要用Winsock.h的話,需要在連結器裡面加入-lwsock32才能用(在工程屬性->參數->連結器裡面)。還有一個問題,就是用thread的話,需要在編譯參數內加上-std=c++11,不然也會編譯錯誤。(用VC的請自動忽略上面這段話)

然後先放個代碼吧。。。寫的不好請見諒(●‘?‘●)

服務端標頭檔(Server.h)

#define _WINSOCK_DEPRECATED_NO_WARNINGS #define  _CRT_SECURE_NO_WARNINGS#ifndef _H_H_#define _H_H_#include <thread>#include <iostream>#include <string>#include <WinSock2.h>#include <stdlib.h>#pragma  comment(lib,"ws2_32.lib")using namespace std;static struct MyStruct{    SOCKET sock;    int empt;}soc[4];class Server{public:    Server();    ~Server();    static void Work(int id);private:    SOCKET s;};#endif

服務端(Server.cpp)

#include "Server.h"Server::Server(){    WSADATA wsadata;    WORD v = MAKEWORD(2, 2);    if (::WSAStartup(v, &wsadata)<0) {cout<<"Failed to start server!"<<endl; system("pause"); exit(0);}    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    sockaddr_in sin;    sin.sin_family = AF_INET;    //伺服器監聽的連接埠和自己的IP    sin.sin_port = htons(13574);    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    if (::bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR) return;    if (::listen(s,5)==SOCKET_ERROR) return; cout<<"The server has been started!"<<endl;    while (true)    {        int i;        for (i=0; i<100; i++)            if (soc[i].empt==0) {soc[i].empt = 1; break;}        sockaddr_in re; int n = sizeof(re);        soc[i].sock = ::accept(s,(SOCKADDR*)&re,&n);        if (soc[i].sock==INVALID_SOCKET) return;        cout<<"New client => "<<soc[i].sock<<endl;        std::thread t(Work,i); t.detach();    }    closesocket(s);}Server::~Server() {::WSACleanup();}void Server::Work(int id){    char Msg[] = "Server => Welcome! Press [Esc] to edit your message, then press [Enter] to send!";    ::send(soc[id].sock,Msg,strlen(Msg),0);    while (true)    {        char buff[1024];        int nR = ::recv(soc[id].sock, buff, 1025, 0);        if (nR>0)        {            buff[nR] = '\0';            cout<<"Received :"<<buff<<" (id:"<<soc[id].sock<<")"<<endl;            for (int i=0; i<100; i++)                if (soc[i].empt) ::send(soc[i].sock,buff,strlen(buff),0);        }        else        {            cout<<"Client => "<<soc[id].sock<<" logout!"<<endl;            closesocket(soc[id].sock);            soc[id].empt = 0; return;        }    }}int main() {Server server; return 0;}

接下來是用戶端(Client.cpp)

#include <iostream>#include <WinSock2.h>#include <thread>#include <string>#include <stdio.h>#include <stdlib.h>#include "TextColor.h"#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)#pragma comment (lib,"ws2_32.lib")using namespace std;SOCKET s;int Status=0;char Name[100000],MyIP[100];void GetMessege(){    char buff[1024];    while (true)    {        int nR = recv(s,buff,1025,0); while (Status);        if (nR>0)        {            buff[nR] = '\0';            cout<<BLUE<<buff<<WHITE<<endl;        }        else if (nR<0) break;    }}bool GetLocalIP(char* ip){    WSADATA wsadata;    int ret=WSAStartup(MAKEWORD(2,2),&wsadata);    if (ret!=0) return false; char hostname[256];    ret=gethostname(hostname,sizeof(hostname));    if (ret==SOCKET_ERROR) return false;    HOSTENT* host=gethostbyname(hostname);    if (host==NULL) return false;    strcpy(ip,inet_ntoa(*(in_addr*)*host->h_addr_list));    return true;}int main(){    if (!GetLocalIP(MyIP)) {cout<<RED<<"Unknow Error!"<<endl; system("pause"); return 0;}    WSADATA wsadata; WORD v=MAKEWORD(2, 2);    if (::WSAStartup(v,&wsadata)<0) {cout<<RED<<"Unknow Error!"<<endl; system("pause"); return 0;}    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    sockaddr_in sin;    sin.sin_family = AF_INET;    //對應伺服器的連接埠和伺服器的IP    sin.sin_port = htons(13574);    sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    if (::connect(s,(SOCKADDR*)&sin,sizeof(sin))<0)    {        cout<<RED<<"Can't connect to the Server!"<<endl;        system("pause"); return 0;    }    cout<<GREEN<<"Connect to the server successfully!"<<endl;    while (true)    {        cout<<WHITE<<"Please input your name:"; gets(Name);        if (strlen(Name)>20) cout<<RED<<"Your name should be less than 20 characters."<<endl;        else break;    }    system("cls");    cout<<RED<<"             ---===Welcome===---"<<WHITE<<endl;    thread t(GetMessege); t.detach();    while (true)    {        Sleep(1000); while (!Status) if (KEY_DOWN(27)) Status^=1;        char Msg[100000],SendMsg[1100]=""; strcat(SendMsg,Name);        strcat(SendMsg,"("); strcat(SendMsg,MyIP); strcat(SendMsg,")");        strcat(SendMsg," => "); setbuf(stdin,NULL);        while (true)        {            cout<<WHITE<<"Input:"; gets(Msg);            if (strlen(Msg)>1000) cout<<RED<<"Your message should be less than 1000 characters."<<endl;            else break;        }        strcat(SendMsg,Msg); ::send(s,SendMsg,strlen(SendMsg),0); Status^=1;    }    closesocket(s); WSACleanup();    return 0;}

這裡用到了一個設定輸出內容顏色的標頭檔(TextColor.h)

#pragma once#include <iostream>#include <windows.h>inline std::ostream& BLUE(std::ostream &s){    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout, FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_INTENSITY);    return s;}inline std::ostream& RED(std::ostream &s){    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout,FOREGROUND_RED|FOREGROUND_INTENSITY);    return s;}inline std::ostream& GREEN(std::ostream &s){    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout,FOREGROUND_GREEN|FOREGROUND_INTENSITY);    return s;}inline std::ostream& YELLOW(std::ostream &s){    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout,FOREGROUND_GREEN|FOREGROUND_RED|FOREGROUND_INTENSITY);    return s;}inline std::ostream& WHITE(std::ostream &s){    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout,FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE);    return s;}struct color{    color(WORD attribute):m_color(attribute){};    WORD m_color;};template <class _Elem, class _Traits>std::basic_ostream<_Elem,_Traits>& operator<<(std::basic_ostream<_Elem,_Traits>& i, color& c){    HANDLE hStdout=GetStdHandle(STD_OUTPUT_HANDLE);     SetConsoleTextAttribute(hStdout,c.m_color);    return i;}

以上是完整的代碼,這裡再提一下整個工程中遇到的問題。

最大的問題還是輸入訊息的問題,一開始是無限迴圈讀入的,後來發現:如果在輸入過程中接收到伺服器發來的訊息,列印出來的話會在輸入的後面,格式都亂了。後來想了幾個辦法解決,一是可以在使用者按下斷行符號後再把之前接收到的訊息列印出來,二是可以設定一個特殊按鍵,按下後可以進入掛起狀態,然後使用者輸入資訊(此過程中不會接受訊息),在使用者按下斷行符號後發送後,就將此過程中接收到的訊息列印出來。仔細考慮了一下,覺得還是第二種方法比較好,於是就用了Esc鍵作為輸入鍵。

最後總結一下此程式還存在的BUG:

1.使用者輸入自己名稱或者訊息時,我是用gets讀入的(不用我說了,gets的漏洞大家都知道吧)。這個漏洞不想改了(才不會告訴你是我懶)。
2.使用者沒有在輸入狀態時,如果隨便按鍵盤,然後按了個斷行符號,[Esc]進入輸入狀態的時候就會把亂按的訊息發出去。(這個東西怪我學識淺薄,即使清空了輸入緩衝區還是會發出去,如果讀者有什麼更好的解決方案,請在評論中寫下,博主感激不盡!!!(? _ ?))

畢竟我只是個高一的蒟蒻。。。程式寫得不咋滴,起碼也算是個入門的教程吧。。。。。。

[多線程通訊程式]C++基於Socket的一款多人線上通訊程式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.