网络聊天程序的设计与实现

网络聊天程序的设计与实现


一、实验题目

网络聊天程序的设计与实现

二、实验目的

使用socket编程,了解socket编程的通信原理,会使用socket进行简单的网络编程,在此基础上编写一聊天程序,能够运行程序,运行客户端和服务器端,实现两个客户端通过服务器端进行通信。

三、背景知识

Tcp协议与Win Sock网络编程接口:
Tcp协议: 是面向连接的运输层协议,提供可靠的、有序的、双向的、面向连接的运输服务。
Win Sock编程: 是一种网络编程接口,实际上是作为TCP/IP协议的一种封装。可以通过调用WinSock的接口函数来调用TCP/IP的各种功能。
WinSock 编程简单流程:WinSock编程分为服务器端和客户端两部分。
TCP 服务器端的大体流程如下: 对于任何基于 WinSock 的编程首先必须要初始化 WinSock DLL 库。 int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )。 wVersionRequested 是我们要求使用的 WinSock 的版本。 调用这个接口函数可以初始化 WinSock 。 然后必须创建一个套接字(Socket) 。WinSock 通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是 IP 地址,一个是 Port 端口号,使用这两个信息,就可以确定网络中 的任何一个通讯节点。
1.3.2设计步骤
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM), 原始套接字(SOCK_RAW);基于 TCP 的 socket 编程是采用的流式套接字。在这个程序中,将两个工 程添加到一个工作区。要链接一个 ws2_32.lib 的库文件(#pragma comment(lib,“ws2_32”))。
服务器端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:绑定套接字到一个 IP 地址和一个端口上(bind());
3:将套接字设置为监听模式等待连接请求(listen());
4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5:用返回的套接字和客户端进行通信(send()/recv());
6:返回,等待另一连接请求; 7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:向服务器发出连接请求(connect());
3:和服务器端进行通信(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
流程图
在这里插入图片描述

四、代码

服务端模块
服务器端开启服务,等待连接,与两个或多个客户端建立连接后,接受其中一个客户端发送的消息,然后将消息内容发送给其他客户端,实现客户端之间的通信。

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <string.h>
#include <strstream>
#include <sstream>

using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define PORT 65432
DWORD WINAPI ThreadFun(LPVOID lpThreadParameter);

SOCKET socketlist[5];		  //定义 客户端 socket数组
volatile int socketindex = 0; // 数组下标
int main()
{
	//初始化winsock2.DLL
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载winsock.dll失败!" << endl;
		return 0;
	}
	//创建套接字
	SOCKET sock_server;
	if ((sock_server = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//绑定端口和Ip
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	if (SOCKET_ERROR == bind(sock_server, (SOCKADDR *)&addr, sizeof(sockaddr_in)))
	{
		cout << "地址绑定失败!错误代码:" << WSAGetLastError() << endl;
		closesocket(sock_server);
		WSACleanup();
		return 0;
	}
	//将套接字设为监听状态
	listen(sock_server, 0);

	//主线程循环接收客户端的连接
	while (1)
	{
		sockaddr_in addrClient;
		int len = sizeof(sockaddr_in);
		//接收成功返回与client通讯的socket
		printf("等待建立连接------\n");
		SOCKET con = accept(sock_server, (SOCKADDR *)&addrClient, &len);

		if (con != INVALID_SOCKET)
		{
			//创建线程 并且传入与client通讯的套接字
			HANDLE hThread = CreateThread(NULL, 0, ThreadFun, (LPVOID)con, 0, NULL);
			CloseHandle(hThread); //关闭对线程的引用
		}
	}
	closesocket(sock_server);
	WSACleanup();
	return 0;
}
//线程通讯部分
DWORD WINAPI ThreadFun(LPVOID lpThreadParameter)
{
	//与客户端通讯 先发送再接收数据
	SOCKET sock = (SOCKET)lpThreadParameter;
	int thisindex = socketindex;
	socketlist[socketindex++] = sock;
	printf("当前的index%d \n", socketindex);
	cout << "成功和" << sock << "建立连接!" << endl;
	while (1)
	{
		char msgbuffer[1000]; //字符缓冲区

		if (strcmp(msgbuffer, "end\0") == 0)
		{
			cout << "服务器端:关闭和" << sock << "的连接!" << endl;
			return 0;
		}

		//接收客户端数据
		msgbuffer[999] = {0};
		int ret = recv(sock, msgbuffer, sizeof(msgbuffer), 0); //获取来自客户端的数据
		if (ret == SOCKET_ERROR || ret == 0)
		{
			cout << sock << "服务器端:断开了连接!" << endl;
			break;
		}
		else
		{
			cout << "接收客户端" << sock << "的消息: " << msgbuffer << endl
				 << endl; //服务器端会显示输出消息
		}
		// char msgbufferr[1000] = {0};
	    
		//发送给其他的socket 客户端
		for (int i = 0; i < socketindex; i++)
		{
			SOCKET socket = socketlist[i];
			if (i != thisindex)
			{ char a =i+1+'0';
        //   strcpy(msgbufferr,"来自客户端 "+a+':'); 
		//   strcat(msgbufferr, msgbuffer);
				int res = send(socket, msgbuffer, sizeof(msgbuffer), 0); //发送消息
				if (res == SOCKET_ERROR || res == 0)
				{
					cout << "发送信息失败!错误代码:" << WSAGetLastError() << endl;
					break;
				}
				else
				{
					printf("成功发送给 %lld !\n\n", socket); // 服务器端成功发送消息给另一端
				}
			}
		}
	}
	return 0;
}

客户端模块
客户端与服务器端建立Tcp连接,能够发送消息给服务器端,接受来自服务器端的消息内容。

#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#define PORT 65432
SOCKET sock_client; //全局变量,用于通信的socket
//两个线程用于接收和发送信息
void sendFun(); //发送信息线程
void recvFun(); //接收信息线程

int main()
{
	HANDLE h1, h2; //线程句柄,其实就是一串数字用来标识线程对象
	//初始化winsock2.DLL
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载winsock.dll失败!" << endl;
		return 0;
	}

	if ((sock_client = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
	{
		cout << "创建套接字失败!错误代码:" << WSAGetLastError() << endl;
		WSACleanup();
		return 0;
	}
	//连接服务器
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //绑定本机的环回地址
	int len = sizeof(sockaddr_in);
	if (connect(sock_client, (SOCKADDR *)&addr, len) == SOCKET_ERROR)
	{
		cout << "连接失败!错误代码:" << WSAGetLastError() << endl;
		return 0;
	}
	//输出当前的客户端号 确认信息
	printf("client %lld \n", sock_client);
	//创建线程后立即执行
	h1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendFun, NULL, 0, NULL);
	h2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvFun, NULL, 0, NULL);
	WaitForSingleObject(h1, INFINITE); //会阻塞,直到线程运行结束参数   dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
	WaitForSingleObject(h2, INFINITE);
	shutdown(sock_client, 2);
	closesocket(sock_client); //关闭套接字

	return 0;

	// char msgbuffer[1000] = {0};
	// //为了实现先收到发过来的消息,所以先输入,在循环里 先接收再输入
	// //从键盘输入一行文字发送给服务器
	// 	cout << "输入发送给服务器端的信息:";
	// 	cin.getline(msgbuffer, sizeof(msgbuffer));
	// 	if (strcmp(msgbuffer, "end\0") == 0)
	// 	{
	// 		cout << "关闭连接!" << endl;
	// 		return 0;
	// 	}
	// 	int ret = send(sock_client, msgbuffer, sizeof(msgbuffer), 0);
	// 	if (ret == SOCKET_ERROR || ret == 0)
	// 	{
	// 		cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
	// 		return 0;
	// 	}
	// //实现交互部分,客户端先接收后发送数据
	// while (1)
	// {
	// 	//接收服务端的消息
	// 	msgbuffer[999] = 0;
	// 	int Ret = recv(sock_client, msgbuffer, sizeof(msgbuffer), 0);//接收信息
	// 	if (strcmp(msgbuffer, "end\0") == 0)
	// 	{
	// 		cout << "服务器端已经关闭连接!" << endl;
	// 		break;
	// 	}
	// 	else
	// 	{
	// 		cout << "\n客户端接受来自服务器端的数据"  << ":" << msgbuffer << endl <<endl;
	// 	}

	// 	//从键盘输入一行文字发送给服务器
	// 	msgbuffer[999] = 0;
	// 	cout << "输入发送给服务器端的信息:";
	// 	cin.getline(msgbuffer, sizeof(msgbuffer));
	// 	if (strcmp(msgbuffer, "end\0") == 0)
	// 	{
	// 		cout << "关闭连接!" << endl;
	// 		break;
	// 	}
	// 	int ret = send(sock_client, msgbuffer, sizeof(msgbuffer), 0);
	// 	if (ret == SOCKET_ERROR || ret == 0)
	// 	{
	// 		cout << "发送信息失败! 错误代码:" << WSAGetLastError() << endl;
	// 		break;
	// 	}

	// }
	// closesocket(sock_client);
	// WSACleanup();
	// return 0;
}
// volatile boolean isrecv = true;
void sendFun()
{
	char buf[128];
	while (1)
	{

		printf("\n客户端的输入信息: ");

		scanf("%s", buf);
		//发送数据
		send(sock_client, buf, strlen(buf) + 1, 0);
	}
}

void recvFun()
{
	char buf[128];
	//接收服务发送的数据
	while (1)
	{

		if (recv(sock_client, buf, 128, 0) > 0)
		{
			if (buf[0] != '\0')
			{
				printf("\n%s%s\n","来自另一个客户端:" ,buf);
				printf("\n客户端的输入信息:"); // 当接收到服务器端的消息
			}	
		}
		else
		{
			break;
		}
	}
}

  • 8
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
摘 要 随着互联网的快速发展,网络聊天工具已经作为一种重要的信息交流工具,受到越来越多的网民的青睐。目前,出现了很多非常不错的聊天工具,其中应用比较广泛的有Netmeeting、腾讯QQ、MSN-Messager等等。该系统开发主要包括一个网络聊天服务器程序和一个网络聊天客户程序两个方面。前者通过Socket套接字建立服务器服务器能读取、转发客户端发来信息,并能刷新用户列表。后者通过与服务器建立连接,来进行客户端与客户端的信息交流。其中用到了局域网通信机制的原理,通过直接继承Thread类来建立多线程。开发中利用了计算机网络编程的基本理论知识,如TCP/IP协议、客户端/服务器端模式(Client/Server模式)、网络编程的设计方法等。在网络编程中对信息的读取、发送,是利用流来实现信息的交换,其中介绍了对实现一个系统的信息流的分析,包含了一些基本的软件工程的方法。经过分析这些情况,该局域网聊天工具采用Eclipse为基本开发环境和java语言进行编写,首先可在短时间内建立系统应用原型,然后,对初始原型系统进行不断修正和改进,直到形成可行系统 关键词:局域网 聊天 socket java 聊天系统各功能模块 (1)服务器程序模块 服务器与客户间通过套接口Socket(TCP)连接。在java中使用套接口相当简单,Java API为处理套接口的通信提供了一个类java.net.Socket,使得编写网络应用程序相对容易。服务器采用多线程以满足多用户的请求,并通过创建一个ServerSocket对象来监听来自客户的连接请求,默认端口为9527,然后无限循环调用accept()方法接受客户程序的连接。 服务器线程源码: package qq.server; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.*; import qq.dao.hibernate.IServiceDao; import qq.entity.*; public class ServerController { private User user; private Socket s; private IServiceDao dao; private ObjectInputStream ois; private ObjectOutputStream oos; private OnlineUser onlineUser; www.bylw120.com public ServerController(Socket s) { super(); dao=ServerMainClass.userDao; this.s = s; } public void handle() throws Exception { ois=new ObjectInputStream(s.getInputStream()); oos=new ObjectOutputStream(s.getOutputStream()); onlineUser=new OnlineUser(ois,oos); while(true){ Request req=(Request)ois.readObject(); ois.read(); RequestType type=req.getType(); if(type.equals(RequestType.exit)){ exitHandle(); break; }else if(type.equals(RequestType.login)){ loginHandle(req); }else if(type.equals(RequestType.register)){ registerHandle(); }else if(type.equals(RequestType.offline)){ offlineHandle(); break; }else if(type.equals(RequestType.changeInformation)){ changeInformationHandle(); }else if(type.equals(RequestType.modifypasswd)){ modifypasswdHandle(req); }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值