[多线程通信程序]C++基于Socket的一款多人在线通信程序

废话不多说,先上图。

进入正题:最近闲着无聊,想起来在初二时用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]进入输入状态的时候就会把乱按的消息发出去。(这个东西怪我学识浅薄,即使清空了输入缓冲区还是会发出去,如果读者有什么更好的解决方法,请在评论中写下,博主感激不尽!!!(? _ ?))

毕竟我只是个高一的蒟蒻。。。程序写得不咋滴,起码也算是个入门的教程吧。。。。。。

原文地址:https://www.cnblogs.com/Alkri/p/9615637.html

时间: 2024-11-08 19:44:01

[多线程通信程序]C++基于Socket的一款多人在线通信程序的相关文章

聊天程序(基于Socket、Thread)

聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现了服务端跟多个客户端之间的聊天,可以群发消息,选择ip发消息,客户端向服务端发送文件. (例子为WinForm应用程序) Socket,端口,Tcp,UDP. 概念 1.Socket还被称作"套接字",应用程序通常通过套接字向网络发送请求或者应答网络请求.根据连接启动的方式以及本地套接字要

基于 WebRTC 创建一款多人联机游戏

本项目的目标旨在尽可能少用服务器资源的前提下研发一款在线多人游戏,同时期望在一个用户的浏览器上运行游戏,同时让另一个玩家来连接.此外还希望程序尽可能简单以便于在博客中分析. 运用的技术 在我刚接触 P2P 网络技术的时候便发现了 WebRTC,并认为这项技术正好适合此项目.WebRTC 是一个新型网络标准旨在给网络浏览器提供即时通信的能力.大部分 WebRTC 案例都是关于建立一个视频或者音频流,但是这项技术也可以用来传输二进制数据.在此项目中,更倾向于使用数据通道将用户的输入传输到主机:游戏状

Windows下基于socket多线程并发通信的实现

本文介绍了在Windows 操作系统下基于TCP/IP 协议Socket 套接口的通信机制以及多线程编程知识与技巧,并给出多线程方式实现多用户与服务端(C/S)并发通信模型的详细算法,最后展现了用C++编写的多用户与服务器通信的应用实例并附有程序. 关键词:Windows:套接字:多线程:并发服务器: Socket 是建立在传输层协议(主要是TCP 和UDP)上的一种套接字规范,最初由美国加州Berkley 大学提出,为UNIX 系统开发的网络通信接口,它定义了两台计算机之间通信的规范,sock

C语言 linux环境基于socket的简易即时通信程序

转载请注明出处:http://www.cnblogs.com/kevince/p/3891033.html   By Kevince 最近在看linux网络编程相关,现学现卖,就写了一个简易的C/S即时通信程序,代码如下: head.h 1 /*头文件,client和server编译时都需要使用*/ 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <sys/types.h> 5 #include <sys

基于Socket的UDP发包程序

UDP(User Datagram Protocol,用户数据报协议)是在互联网中常用的传输层协议,该协议提供了向另一用户程序发送的消息的最简便的协议机制.与TCP一样,其默认的下层协议是IP.UDP是面向操作的,不提供提交和复制保护,因此不能保证数据的可靠性传输.UDP一般用在可靠性较高的局域网中. .NET 下可使用Socket 类编写基于UDP的网络程序,只要在创建Socket时将构造函数函数的第三个参数选为枚举值ProtocolType.Udp即可.此外,.NET的System.Net.

一个基于Socket的http请求监听程序实现

首先来看以下我们的需求: 用java编写一个监听程序,监听指定的端口,通过浏览器如http://localhost:7777来访问时,可以把请求到的内容记录下来,记录可以存文件,sqlit,mysql数据库,然后把接受到的信息在浏览器中显示出来 要点: Socket,线程,数据库,IO操作,观察者模式 来看下我们如何来设计这个小系统,这个系统包含三部分的内容,一个是监听端口,二是记录日志,三是数据回显,端口监听第一想到的就是Socket编程了,数据回显也是一样的,无非是把当前请求客户端的sock

php 基于socket的基本通信

php 基于socket的基本通信 1.前言 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议. 这种介绍度娘很多,希望了解更多的自行百度,往下看的朋友记得先开启socket扩展: 2.创建socke服务器端 基本步骤: 初始化86socket 端口绑定 端口进行监听 调用accept阻塞

AF_UNIX域通信(基于socket和pipe的通信,只适于UNIX系统S&C同在一个主机上,用于进程通信)

服务器端: #include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include <sys/socket.h>#include <sys/un.h>#include <stddef.h>char buf[100];void main

基于Socket创建Web服务

基于Socket创建Web服务 为什么要使用Socket呢,我们来看下图 Socket原理图回顾: -------------------编写SocketService,完成字母小写转大写功能----------------------------- ServerSocket服务器端代码如下: public static void main(String[] args) throws IOException { // 1:建立服务器端的tcp socket服务,必须监听一个端口 ServerSo