使用C/C++实现Socket聊天程序
Initsock.h文件
- // initsock.h文件
- #include
- #include
- #include
- #include
- #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib
- class CInitSock
- {
- public:
- CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
- {
- // 初始化WS2_32.dll
- WSADATA wsaData;
- WORD sockVersion = MAKEWORD(minorVer, majorVer);
- if(::WSAStartup(sockVersion, &wsaData) != 0)
- {
- exit(0);
- }
- }
- ~CInitSock()
- {
- ::WSACleanup();
- }
- };
TCP版
TCPClient.cpp文件
- //////////////////////////////////////////////////////////
- // TCPClient.cpp文件
- /*
- 《使用说明》
- 0.运行程序前请查看是否将initsock.h
- 文件引入到项目中。
- 1.首先修改聊天对方的IP地址
- 2.请首先运行服务端(TCPServer)程序,再运行客户端(TCPClient)程序:
- 如配置正确服务端会收到相关连接信息。
- 3.连接成功后,需要由服务器端首先发起会话(输入消息并确认发送),
- 客户端收到消息后才能输入消息并确认发送到服务器端。
- 并且双方每次只能发送一条消息。如想发送第二条消息,需要等待该方成功
- 接受到另一方的消息后才能继续输入消息。
- */
- #include "InitSock.h"
- #include
- #include
- CInitSock initSock; // 初始化Winsock库
- int main()
- {
- // 创建套节字
- SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(s == INVALID_SOCKET)
- {
- printf(" Failed socket() /n");
- return 0;
- }
- // 也可以在这里调用bind函数绑定一个本地地址
- // 否则系统将会自动安排
- // 填写远程地址信息
- sockaddr_in servAddr;
- servAddr.sin_family = AF_INET;
- servAddr.sin_port = htons(4567);
- // 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
- // 如果你的计算机没有联网,直接使用127.0.0.1即可
- servAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.129");
- if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
- {
- printf(" Failed connect() /n");
- return 0;
- }
- char buff[256];
- char szText[256] ;
- while(TRUE)
- {
- //从服务器端接收数据
- int nRecv = ::recv(s, buff, 256, 0);
- if(nRecv > 0)
- {
- buff[nRecv] = '/0';
- printf("接收到数据:%s/n", buff);
- }
- // 向服务器端发送数据
- cin>>szText ;
- szText[255] = '/0';
- ::send(s, szText, strlen(szText), 0) ;
- }
- // 关闭套节字
- ::closesocket(s);
- return 0;
- }
TCPServer.cpp文件
- // TCPServer.cpp文件
- /*
- 《使用说明》
- 0.运行程序前请查看是否将initsock.h
- 文件引入到项目中。
- 1.首先修改聊天对方的IP地址
- 2.请首先运行服务端(TCPServer)程序,再运行客户端(TCPClient)程序:
- 如配置正确服务端会收到相关连接信息。
- 3.连接成功后,需要由服务器端首先发起会话(输入消息并确认发送),
- 客户端收到消息后才能输入消息并确认发送到服务器端。
- 并且双方每次只能发送一条消息。如想发送第二条消息,需要等待该方成功
- 接受到另一方的消息后才能继续输入消息。
- */
- #include "InitSock.h"
- #include
- #include
- CInitSock initSock; // 初始化Winsock库
- int main()
- {
- // 创建套节字
- SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(sListen == INVALID_SOCKET)
- {
- printf("Failed socket() /n");
- return 0;
- }
- // 填充sockaddr_in结构
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(4567);
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- // 绑定这个套节字到一个本地地址
- if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf("Failed bind() /n");
- return 0;
- }
- // 进入监听模式
- if(::listen(sListen, 2) == SOCKET_ERROR)
- {
- printf("Failed listen() /n");
- return 0;
- }
- // 循环接受客户的连接请求
- sockaddr_in remoteAddr;
- int nAddrLen = sizeof(remoteAddr);
- SOCKET sClient = 0;
- char szText[] = " TCP Server Demo! /r/n";
- while(sClient==0)
- {
- // 接受一个新连接
- sClient = ::accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
- if(sClient == INVALID_SOCKET)
- {
- printf("Failed accept()");
- }
- printf("接受到一个连接:%s /r/n", inet_ntoa(remoteAddr.sin_addr));
- continue ;
- }
- while(TRUE)
- {
- // 向客户端发送数据
- gets(szText) ;
- ::send(sClient, szText, strlen(szText), 0);
- // 从客户端接收数据
- char buff[256] ;
- int nRecv = ::recv(sClient, buff, 256, 0);
- if(nRecv > 0)
- {
- buff[nRecv] = '/0';
- printf(" 接收到数据:%s/n", buff);
- }
- }
- // 关闭同客户端的连接
- ::closesocket(sClient);
- // 关闭监听套节字
- ::closesocket(sListen);
- return 0;
- }
UDP版
- // Chat.cpp : Defines the entry point for the console application.
- //
- /*
- 《使用说明》
- 0.运行程序前请查看是否将initsock.h
- 文件引入到项目中。
- 1.首先修改聊天对方的IP地址
- 2.运行程序:如配置正确另一方会收到相关连接信息。
- 3.输入消息:在每次输入完欲发送的消息后,需要连续敲击两次回车。
- 4.本程序有诸多缺陷:对用户输入的消息不能即时回显到控制台,
- 需要在敲击两次回车后回显到屏幕。
- */
- #include "stdafx.h"
- #include
- #include
- #include
- #include
- #include "InitSock.h"
- using namespace std;
- CInitSock initSock; // 初始化Winsock库
- DWORD receiverMark ; //接收消息者线程标识符
- DWORD senderMark ;//发送者线程标识符
- /**
- *定义信号量
- */
- DWORD WINAPI Receiver(LPVOID) ;
- DWORD WINAPI Sender(LPVOID) ;
- // 接收数据
- char buff[1024];
- sockaddr_in addr;
- int nLen = sizeof(addr);
- SOCKET s ;
- int main(int argc, char *argv[])
- {
- // 创建套节字
- s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- //u_long iMode = 1;
- //ioctlsocket(s, FIONBIO, &iMode);
- if(s == INVALID_SOCKET)
- {
- printf("Failed socket() /n");
- return 0;
- }
- // 填充sockaddr_in结构
- sockaddr_in sin;
- sin.sin_family = AF_INET;
- sin.sin_port = htons(4567);
- sin.sin_addr.S_un.S_addr = INADDR_ANY;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(4567);
- // 注意,这里要填写服务器程序所在机器的IP地址
- // 如果你的计算机没有联网,直接使用127.0.0.1即可
- addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.129");
- // 绑定这个套节字到一个本地地址
- if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
- {
- printf("Failed bind() /n");
- return 0;
- }
- // 发送数据
- char szText[] = "PC请求连接... /r/n";
- ::sendto(s, szText, strlen(szText), 0, (sockaddr*)&addr, sizeof(addr));
- CreateThread(NULL,0,Receiver,NULL,0,&receiverMark);
- CreateThread(NULL,0,Sender,NULL,0,&senderMark);
- bool isContinue = true ;
- while(isContinue)
- {
- if(getche()==96){ //按~后终止程序运行
- isContinue = false;
- }
- }
- system("PAUSE");
- return 0;
- }
- /**
- *接收者
- */
- DWORD WINAPI Receiver(LPVOID lpParam )
- {
- while(1)
- {
- int nRecv = ::recvfrom(s, buff, 1024, 0, (sockaddr*)&addr, &nLen);
- if(nRecv > 0)
- {
- buff[nRecv] = '/0';
- printf(" Received data(%s):%s/n", ::inet_ntoa(addr.sin_addr), buff);
- }
- }
- return 0;
- }
- /**
- *发送者
- */
- DWORD WINAPI Sender(LPVOID lpPara)
- {
- while(1)
- {
- cout<<"Input your message: " ;
- // 发送数据
- char text[256] ;
- cin>>text ;
- text[255] = '/0' ;
- cout<
- ::sendto(s, text, strlen(text), 0, (sockaddr*)&addr, sizeof(addr));
- }
- return 0;
- }
鉴于很多同学说拷贝的代码无法运行,
特此将源代码和实验报告上传于此:下载
解压密码:yexianyi20081216
北航软件学院《一级实践》实验报告
学号:GS0821594 姓名:叶现一 第 13 周
内容
训练
使用C/C++实现Socket聊天程序
解决该问题的思路
解决该问题的整体方案不外有二:
(1)基于TCP的Socket连接;(2)基于UDP的Socket连接;
但是,针对每种方案又各有很多具体的实现方法。
在本次实验中,我先后开发了基于TCP连接和UDP连接的Socket聊天程序。具体实现思路如下:
(一) 基于TCP连接Socket聊天程序
基于该连接的聊天程序需要至少具备一个服务器端(Server)和一个客户端(Client)。在本程序中,一个用户作为Server端,另一个用户作为Client端。也就是说,作为Server端的用户,需要首先启动程序,等待Client端的连接请求。当TCP连接握手以后,双方方可进行交互。(注:在本程序中Server端并不是单独存在。它也可以向他的Client端发送消息。)但是本程序实现的交互功能十分简单,具有很多限制。当Client端与Server端握手以后,Server端需要首先发起会话;Client端在收到消息后再回复一条消息给Server端;同样,Server端在收到消息后再回复一条消息给Client端……以此类推。并且,无论是Server端还是Client端每次发送消息只能发送一条。
造成交互操作具有诸多限制的主要原因是,我在Server端和Client端使用了一个While循环,它们的伪代码分别如下:
Client端
Server端
while(TRUE)
{
从Server端接收消息
{
…
}
向Server端发送消息
{
…
}
}
while(TRUE)
{
向Client端发送消息
{
…
}
从Client端接收消息
{
…
}
}
(二) 基于UDP连接Socket聊天程序
基于该连接的聊天程序不需要具备服务器端(Server),每个客户端(Client)既是服务器端也是客户端。也就是说每个Client端自身既可以自行接收其它用户发来的消息,也可以向其它Client端发送消息,不需要事先与其他用户进行握手连接。
由于在默认情况下WinSock接口的recvfrom()和sendto()都会在被调用时阻塞当前线程,也就是说如果程序正在接受其他用户发来的数据,那么它就不能够执行发送数据的任务,反之相同。所以为解决该问题一般有以下几种解决方案:采用Select模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠(Overlapped)模型和完成接口(Completion port)模型。在本程序中,由于我没能在短时间内学会上述方案中的任一种,因此采用了多线程技术去实现消息接收和发送的同步问题。也就是说,在程序中创建两个线程,一个线程负责发送消息,另一个消息负责接受消息。两个线程交替运行,从而达到同时收发消息的目的。当然采用多线程方式解决消息收发同步问题可以移除上个程序中每个用户一次只能发送一条消息的限制。
本周开发源代码
代码的功能简述
使用C/C++实现Socket聊天程序:
TCP版:服务器端用户和客户端用户在成功连接后,其中一方通过控制台输入消息,依次轮流向另一方发送数据。要求,服务器端首先发起会话,并且双方每次只能发送一条消息。
UDP版:任一端用户通过指定IP地址将消息发送到另一端的用户。交互双方通过控制台输入消息向另一方发送数据。没有任何发送限制。
开发的收获
理解了TCP连接和UDP连接的基本原理和工作过程。
复习了关于Windows多线程及进程同步的相关知识。
开发中碰到的主要困难
对于TCP版:
在考虑如何解决消息的收发同步问题上遇到了困难。最终使用了不佳方案:通过在服务器端和客户端分别运行while循环并依次进行数据收发工作的方式解决数据收发同步问题。
对于UDP版:
同样在考虑如何解决消息的收发同步问题上遇到了困难。但这次使用了多线程解决该问题。
开发中未能解决的问题
对于UDP版:
为何第一次消息输入完毕敲击一次回车后,只有消息的第一个字符没能发送出去,而其它字符却可以被成功发送出去?而且当第二次输入消息敲回车后消息就能被全部发送出去?
为何消息输入完毕后需要按两次回车键才能将消息发送到另一端?
为什么输入的消息不能即时回显到发送者屏幕上?只有当敲击二次回车后用户输入的欲发送消息才会显现出来?
如何才能避免用户在输入消息的同时也能正常接收消息?也就是不至于打断用户已输入的消息的前提下,显示接收到的消息。
针对本周训练内容自己设计的案例
案例的主要功能
同代码的功能简述
用到的基本知识
相关Winsock编程接口;TCP连接和UDP连接基本工作原理;Windows多线程;
进程同步
程序注意了哪些规范
代码格式、常量的定义、函数的声明
你进行了哪些测试
略
程序进行了哪些有效性检查
略
你是如何保证程序的高效率的
略
注意:实验报告和案例源代码须在本次小组讨论会前提交