网络编程大作业-聊天室

前言:

大二时候完成的网络编程大作业,用C\C++语言写的一款简易聊天室。目前一个服务器只能连两个客户端,两个以上的我搞不出来。也可以在三台电脑操作,只要输入服务器的IP地址就行了。
使用的编译器:VS2010、VS2019

演示一下:

左边的是服务器,中间的是客户端1:zhang san,右边是客户端2:li si。
在这里插入图片描述

服务器源码:

#include "WinSock2.h"    // winsock2.h是套接字接口。
#include "process.h"     //process.h 是包含用于和宏指令的作用声明与螺纹和过程一起使用的C标头文件。
#include "stdio.h"       //被包含的文件通常是由系统提供的,其扩展名为.h,而stdio为standard input output的缩写,意为“标准输入输出”
#include "stdlib.h"      //stdlib 头文件里包含了C语言的一些函数,该文件包含了的C语言标准库函数的定义
#include "conio.h"      //将conio.h包含入你的程序,使你可以引用其中声明的函数。conio.h不是C标准库中的头文件。
#pragma comment(lib,"ws2_32.lib")   // ws2_32.lib是套接字实现。
#define SEND_OVER 1                //已经转发消息
#define SEND_YET 0                 //还没转发消息
int Status = SEND_YET;             //状态设为未发送

sockaddr_in ClientAddr = { 0 };      //客户端地址
HANDLE g_hRecv1 = NULL;   //handle为句柄,用表示对象   void *g_hRecv1=NULL;
HANDLE g_hRecv2 = NULL;

//客户端信息结构体
 struct Client_inf
{
  SOCKET sClient;      //客户端套接字
  char buf[512];       //数据缓冲区
  char userName[20];   //客户端用户名
  char IP[20];         //客户端IP
  UINT_PTR flag;       //标记客户端,用来区分不同的客户端,typedef unsigned int_w64 UINT_PTR,是为解决32位与64位编译器的兼容性而设置的关键字
};
 typedef Client_inf Client;
 
Client ClientSock[2] = { 0 };         //创建一个客户端结构体
SOCKET ServerSocket=INVALID_SOCKET;
 
unsigned __stdcall ThreadSend(void* param);    //声明发送数据的线程函数
unsigned __stdcall ThreadRecv(void* param);     //声明接收数据的线程函数
unsigned __stdcall ThreadAccept(void* param);   //声明接受请求的线程函数
 
int main(void)
{
	int ret,len;
	WSADATA data ;
  struct sockaddr_in ServerAddr;       //服务端地址
  unsigned short SERVERPORT = 6666;     //服务器监听端口
 
  //初始化套接字
   ret = WSAStartup(MAKEWORD(2,2),&data);//WSAStartup函数的返回值是0表示成功
  if (SOCKET_ERROR == ret)
  {
	printf("WSAstartup error!\n");
    return -1;
  }
 
  //创建套接字
  ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (INVALID_SOCKET == ServerSocket)
  {
	  printf("创建socket失败!\n");
    return -2;
  }
 
  //设置服务器地址
  ServerAddr.sin_family = AF_INET;//连接方式
  ServerAddr.sin_port = htons(SERVERPORT);//服务器监听端口
  ServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//任何客户端都能连接这个服务器
  //绑定服务器
  ret = bind(ServerSocket, (struct sockaddr*)&ServerAddr, sizeof(sockaddr));//bind是一组用于函数绑定的模板。
  if (SOCKET_ERROR == ret)
  {
	printf("bind error!\n");
    closesocket(ServerSocket);
	WSACleanup();
    return -3;
  }
  //设置监听客户端连接数
  ret =listen(ServerSocket,2);//listen是创建一个套接口并监听申请的连接.
  if (SOCKET_ERROR == ret)
  {
  
	printf("listen error!\n");
    closesocket(ServerSocket);
    WSACleanup();
    return -4;
  }
  printf("开启聊天成功,等待用户连接....\n");

  _beginthreadex(NULL, 0, ThreadAccept, NULL, 0, 0);    //启动接受连接线程
  int k=0;
  while(k<100)   //让主线程休眠,不让它关闭TCP连接.
  {
	  Sleep(10000000);
	  k++;
  }
  //关闭套接字
  for (int j = 0;j < 2;j++)
  {
    if (ClientSock[j].sClient != INVALID_SOCKET)
      closesocket(ClientSock[j].sClient);
  }
  closesocket(ServerSocket);
  WSACleanup();
  return 0;
}

//发送数据线程函数的定义
unsigned __stdcall ThreadSend(void* param)    //param形参
{
  int ret = 0;
  int flag = *(int*)param;        //int*是把param从void*强制转化为int*,要不然取值的时候系统会不知所措,外面的*就是取值操作,取param这个地址里面保存的整型值
  SOCKET client = INVALID_SOCKET;         //创建一个临时套接字来存放要转发的客户端套接字
  char temp[512] = { 0 };             //创建一个临时的数据缓冲区,用来存放接收到的数据
  memcpy(temp,  ClientSock[!flag].buf, sizeof(temp));   //拷贝
 sprintf( ClientSock[flag].buf, "%s: %s", ClientSock[!flag].userName, temp);//添加一个用户名头
 
  if (strlen(temp) != 0 && Status == SEND_YET&&temp!="\n"&&(*temp!=' ')) //如果数据不为空且还没转发则转发
  {
	  ret = send( ClientSock[flag].sClient, ClientSock[flag].buf, sizeof( ClientSock[flag].buf), 0);
  }//send()是一个计算机函数,功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
  if (SOCKET_ERROR == ret)
  { 
	  return 1;
  }
  Status = SEND_OVER;  //转发成功后设置状态为已转发
  return 0;
}

//接受数据线程函数的定义
unsigned __stdcall ThreadRecv(void* param)
{
  SOCKET client = INVALID_SOCKET;    //客户端套接字
  int flag = 0; 
  if (*(int*)param ==  ClientSock[0].flag)      //判断是哪个客户端发来的消息
  {
    client =  ClientSock[0].sClient;
    flag = 0;
  }  
  else if (*(int*)param ==  ClientSock[1].flag)
  {
    client =  ClientSock[1].sClient;
    flag = 1;
  }
  char temp[512] = { 0 }; //临时数据缓冲区
  while (1)
  {
    memset(temp, 0, sizeof(temp));
    int ret = recv(client, temp, sizeof(temp), 0); //接收数据
    if (SOCKET_ERROR == ret)
      continue;
    Status = SEND_YET;                //设置转发状态为未转发
	if(client==ClientSock[0].sClient )    
	{                                    //设置防止出现自己给自己发消息的BUG
		flag=1;
	}
	else
	{
		flag=0;
	}  
    memcpy( ClientSock[!flag].buf, temp, sizeof( ClientSock[!flag].buf));
    _beginthreadex(NULL, 0, ThreadSend, &flag, 0, NULL); //开启一个转发线程,flag标记着要转发给哪个客户端
   
  }
 
  return 0;
}

//接受请求线程函数的定义
unsigned __stdcall ThreadAccept(void* param)
{
 
  int i = 0;
  int temp1 = 0, temp2 = 0;

  {
    while (i < 2)      //表明只允许两个客户端连接
    {
      if (ClientSock[i].flag != 0)
      {
        ++i;
        continue;
      }
      //如果有客户端申请连接就接受连接
	  int len= sizeof(ClientAddr);
	  int ret = ClientSock[i].sClient = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &len);//accept()是在一个套接口接受的一个连接。
      if (INVALID_SOCKET == ret)
      {
		  printf("accept error!\n");
        closesocket(ServerSocket);
        WSACleanup();
        return -1;
      }
      recv(ClientSock[i].sClient, ClientSock[i].userName, sizeof(ClientSock[i].userName), 0); //接收用户名
      printf(" 成功连上聊天用户!IP:%s ,Port: %d,UerName: %s\n",inet_ntoa(ClientAddr.sin_addr), htons(ClientAddr.sin_port), ClientSock[i].userName);
      memcpy(ClientSock[i].IP, inet_ntoa(ClientAddr.sin_addr), sizeof(ClientSock[i].IP)); //记录客户端IP
      ClientSock[i].flag = ClientSock[i].sClient; //不同的socke有不同UINT_PTR类型的数字来标识
      i++;
	}
    i = 0;
 
    if (ClientSock[0].flag != 0 && ClientSock[1].flag != 0 )         //当两个用户都连接上服务器后才进行消息转发
    {
      if (ClientSock[0].flag != temp1)   //每次断开一个连接后再次连上会新开一个线程,导致cpu使用率上升,所以要关掉旧的,,int temp1 = 0, temp2 = 0;
      {
          if (g_hRecv1)         //这里关闭了线程句柄
          CloseHandle(g_hRecv1);
        g_hRecv1 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, &ClientSock[0].flag, 0, NULL); //开启2个接收消息的线程
      }  
      if (ClientSock[1].flag != temp2)
      {
          if (g_hRecv2)
          CloseHandle(g_hRecv2);
        g_hRecv2 = (HANDLE)_beginthreadex(NULL, 0, ThreadRecv, &ClientSock[1].flag, 0, NULL);
	  }    
	}
    temp1 = ClientSock[0].flag; //防止ThreadRecv线程多次开启
    temp2 = ClientSock[1].flag;
   
    Sleep(2000);
	}
  return 0;
  }

客户端源码:

#include "WinSock2.h"    // winsock2.h是套接字接口。
#include "process.h"    //process.h 是包含用于和宏指令的作用声明与螺纹和过程一起使用的C标头文件。
#include "stdio.h"     //被包含的文件通常是由系统提供的,其扩展名为.h,而stdio为standard input output的缩写,意为“标准输入输出”
#include "stdlib.h"    //stdlib 头文件里包含了C语言的一些函数,该文件包含了的C语言标准库函数的定义
#include "conio.h"     //将conio.h包含入你的程序,使你可以引用其中声明的函数。conio.h不是C标准库中的头文件。
#pragma comment(lib,"ws2_32.lib")     // ws2_32.lib是套接字实现。
#define RECV_OVER 1
#define RECV_YET 0
char userName[20] = { 0 };
int Status = RECV_YET;
unsigned __stdcall ThreadRecv(void* param);  //接受数据的线程
unsigned __stdcall ThreadSend(void* param);  //发送数据的线程
int main(void)
{
	WSADATA wsaData = { 0 };      
	SOCKET ClientSocket = INVALID_SOCKET;   //客户端套接字
  	sockaddr_in ServerAddr = { 0 };     //服务端地址,SOCKADDR_IN为结构体,可以小写
 	unsigned short SERVERPORT = 6666; 
//初始化套接字

 WSADATA data;
  int ret=WSAStartup(MAKEWORD(2,2),&data);   //WSAStartup函数的返回值是0表示成功 
  if (SOCKET_ERROR==ret)        
  {
	printf("WSAStartup 启动错误!\n");               
    return -1;
  }
  
  //创建套接字
  ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if ( INVALID_SOCKET == ClientSocket)
  {
    
	  printf("socket创建失败!\n");
	  WSACleanup();
      return -1;
  }
  //输入服务器IP
  printf("请输入服务器的IP:");
  char IP[32] = { 0 };
  gets_s(IP,31);
  //设置服务器地址
  ServerAddr.sin_family = AF_INET;  
  ServerAddr.sin_port = htons(SERVERPORT);           //服务器端口
  ServerAddr.sin_addr.S_un.S_addr = inet_addr(IP);   //服务器地址
  printf("正在努力连接服务器.....\n");
  //连接服务器
  ret=connect(ClientSocket,(sockaddr*)&ServerAddr,sizeof(ServerAddr));
  if ( SOCKET_ERROR ==ret)   
  {
	  printf("connect连接服务器失败!\n");
	  closesocket(ClientSocket);
      WSACleanup();
      return -1;
  }
  printf("成功连上服务器 IP:%s Port:%d\n",IP,htons(ServerAddr.sin_port));
  printf("欢迎登录66微聊聊天室!\n");
  printf("请输入你的名字: ");
  gets_s(userName,20);
  send(ClientSocket, userName, sizeof(userName), 0);
  printf("\n\n");
  _beginthreadex(NULL, 0, ThreadRecv, &ClientSocket, 0, NULL); //启动接收和发送消息线程
  _beginthreadex(NULL, 0, ThreadSend, &ClientSocket, 0, NULL);
  for (int k = 0;k < 1000;k++)         // 让主线程休眠,不让它关闭TCP连接
  {
	  
	  Sleep(10000000);
  }
  closesocket(ClientSocket);
  WSACleanup();
  return 0;
}

unsigned __stdcall ThreadRecv(void* param)
{
	char buf[512] = { 0 };
  while (1)
  {
    int ret = recv(*(SOCKET*)param, buf, sizeof(buf), 0);   //先强行转化为SOCKET*,不然void*直接引用会出错
    if (SOCKET_ERROR == ret)
    {
      Sleep(500);          //将进程挂起一段时间,即停下来0.5s再继续
      continue;
    }
    if (strlen(buf) != 0)    //判断缓冲区是不是有数据,有就打印出来!
    {
      printf("%s\n", buf);
      Status = RECV_OVER;     //把状态设为已接收
    }
    else
      Sleep(100);  
  }
  return 0;
}

unsigned __stdcall ThreadSend(void* param)
{
	char buf[512] = { 0 };
  int ret = 0;
  while (1)
  {
   int c = getch();                       //在头文件conio.h有定义,此函数是一个不回显函数,当用户按下某个字符时,此函数自动读取,无需按回车
    if(c == 72 || c == 0 || c == 68)       //遇到这几个值getch就会自动跳过
      continue;        
    printf("%s: ", userName);
    gets_s(buf,511);                       //此函数在stdio.h中定义,第二参数就是允许输入长度,留一位补零,否则溢出
    ret = send(*(SOCKET*)param, buf, sizeof(buf), 0);
    if (ret == SOCKET_ERROR)
      if (ret == SOCKET_ERROR)
	{
		return 1;
	}
	if(strcmp(buf,"bye") == 0||strcmp(buf,"再见")==0) 
	{
		printf("您已退出聊天室!\n");
		break;
	}
  }
  return 0;
}

最后:
需要代码的可以自行下载。下载链接
下载操作:
在这里插入图片描述

  • 11
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
以下是Java网络编程实现仿QQ聊天室的基本步骤: 1. 创建服务器端和客户端的Socket对象,指定IP地址和端口号。 2. 服务器端使用ServerSocket类的accept()方法监听客户端的连接请求,客户端使用Socket类的connect()方法连接服务器。 3. 服务器端使用Socket类的getInputStream()和getOutputStream()方法获取输入输出流,客户端使用同样的方法获取输入输出流。 4. 服务器端使用输入输出流实现与客户端的通信,客户端同样使用输入输出流实现与服务器端的通信。 5. 服务器端使用多线程实现多个客户端的同时连接和通信。 6. 实现聊天室的基本功能,例如群聊、私聊、发送文件等。 以下是一个简单的Java网络编程实现仿QQ聊天室的代码示例: 服务器端代码: ```java import java.io.*; import java.net.*; public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务器已启动,等待客户端连接..."); while (true) { Socket socket = serverSocket.accept(); System.out.println("客户端已连接,IP地址为:" + socket.getInetAddress().getHostAddress()); new ServerThread(socket).start(); } } } class ServerThread extends Thread { private Socket socket; public ServerThread(Socket socket) { this.socket = socket; } public void run() { try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String message; while ((message = br.readLine()) != null) { System.out.println("客户端说:" + message); } socket.shutdownInput(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 客户端代码: ```java import java.io.*; import java.net.*; public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); String message; while ((message = br.readLine()) != null) { pw.println(message); } socket.shutdownOutput(); pw.close(); br.close(); socket.close(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

⁽⁽ଘ晴空万里ଓ⁾⁾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值