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

一、实验目的

了解Socket通信的原理,在此基础上编写一个聊天程序。

二、总体设计

1. 基本原理

socket通信原理是一种“打开——读/写——关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向文件写入内容供对方读取或者读取对方的内容,通讯结束时关闭文件。
Socket在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。
Socket保证了不同计算机之间的通信,对于网站,通信模型是服务器与客户端之间的通信。两端都建立一个socket对象,然后通过socket对象对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。

2. 设计步骤

(1)服务器端编程的步骤

①加载套接字库,创建套接字WSAStartup();
在使用socket之前要进行版本的设定和初始化,应用程序只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。根据版本初始化windows socket,返回0表示成功。
②创建套接字,使用TCP协议;
有套接字的接口才能进行通信。
③绑定套接字到一个 IP 地址和一个端口上(bind());
用bind()函数确定socket各种属性。
④将套接字设置为监听模式等待连接请求(listen());
⑤请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
accept()是一个阻塞函数,如果没有客户端请求,连接会一直等待在这里。该函数会返回一个新的套接字,这个新的套接字是用来与客户端通信的套接字,之前那个套接字是用来监听的套接字。
⑥用返回的套接字和客户端进行通信(send()/recv());
⑦返回,等待客户端的另一连接请求;
⑧关闭套接字,关闭加载的套接字库(closesocket())。

(2)客户端编程的步骤

①加载套接字库,创建套接字WSAStartup();
要连接的服务器的ip,因为现在服务器端就是本机,所以写本机ip,127.0.0.1一个特殊的IP地址,表示是本机的IP地址。
②向服务器发出连接请求(connect());
如果没有成功连接到服务器,一直循环,直至连接上为止。
③和服务器端进行通信(send()/recv());
④关闭套接字,关闭加载的套接字库(closesocket())。

三、详细设计

1. 程序流程图

在这里插入图片描述

2. 实验代码

(1)服务器端

#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment (lib,"ws2_32.lib")
SOCKET sockConn;//全局变量,用来通信的套接字
//把发送消息和接收信息的功能封装成两个函数作为线程使用。
void recvFun();//接收信息线程
void sendFun();//发送信息线程
void createConnect();//创建通信套接字
int flag=0;
HANDLE h1, h2;//线程句柄
int main()
{

    SOCKET serverSocket;//监视的套接字
    //SOCKET sockConn;//用来通信的套接字
    SOCKADDR_IN newAddr;//保存客户端的socket地址信息
    SOCKADDR_IN addr;//地址结构体,包括ip port(端口)
    WSADATA data;//存储被WSAStartup函数调用后返回的Windows Sockets数据
    WORD version;//socket版本
    int info;

    //在使用socket之前要进行版本的设定和初始化
    version = MAKEWORD(2, 2);//设定版本
    info = WSAStartup(version, &data);
    /*应用程序或DLL只能在一次成功的WSAStartup()调用之后
    才能调用进一步的Windows Sockets API函数。
    根据版本初始化 windows socket,返回0表示成功
    */
    if (info != 0)
    {
        printf("初始化失败\n");
        return 0;
    }
    if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2)
    {
        printf("加载失败\n");
        WSACleanup();
        return 0;
    }
    //创建套接字,使用TCP协议
    //有套接字的接口才能进行通信
    serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//AF_INET使用IPV4地址,SOCK_STREAM使用流传输,IPPROTO_TCP使用TCP协议
    addr.sin_addr.S_un.S_addr = htonl(ADDR_ANY);//表示任何的ip过来连接都接受
    addr.sin_family = AF_INET;//使用ipv4的地址
    addr.sin_port = htons(0606);//设定应用占用的端口
    //用bind()函数确定socket各种属性
    bind(serverSocket, (SOCKADDR*)&addr, sizeof(SOCKADDR));//将套接字serverSocket与端口0606,接收的ip绑定
    listen(serverSocket, 3);//开始监听,是否有客服端请求连接,最大连接数为3
    printf("开始监听,等待客户端连接..........\n");
    int len = sizeof(SOCKADDR);
    //accept是一个阻塞函数,如果没有客户端请求,连接会一直等待在这里
    //该函数会返回一个新的套接字,这个新的套接字是用来与客户端通信的套接字,之前那个套接字是监听的套接字
    while(1){
         sockConn=accept(serverSocket,(SOCKADDR*)&newAddr,&len);//接受客户端的请求
         printf("连接成功......\n");
         //创建线程后立即运行
         //第一个参数表示线程内核对象的安全属性;第二个参数表示线程栈空间大小;第三个参数表示新线程所执行的线程函数地址(函数的名字),多个线程可以使用同一个函数地址
         //第四个参数是传递给线程函数的参数;第五个参数指定什么时候调用线程,为0表示线程创建之后就可以进行调用;第六个参数返回线程的ID号,传入NULL表示不需要返回该线程ID号
         h1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)sendFun, NULL, 0, NULL);//用于发送的线程
         h2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvFun, NULL, 0, NULL);//用于接收的线程
     }
    closesocket(sockConn);//关闭套接字
    return 0;
}
void sendFun()
{
    char buf[128];
    while (1)
    {
        printf("服务器端的信息:");
        scanf("%s", buf);
        //sockConn为用来通信的套接字,buf为发送数据的缓冲区,strlen(buf)+1为发送数据的长度,0位flags标志
        send(sockConn, buf, strlen(buf) + 1, 0);//发送信息
    }
}

void recvFun()
{
    char buf[128];
    while (1)
    {
        int Ret = recv(sockConn, buf, 128, 0);//接收信息
        if(Ret<0){
            printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("客户端已退出,服务器端准备监听\n");
            flag=flag%2+1;
            break;
        }else if(Ret==0){
            printf("ERROR_RECV");
        }else{
            printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("%s%s\n","客户端的信息: ", buf);
            printf("服务器端的信息:");
        }
    }
}

(2)客户端

#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
SOCKET sockClient;//全局变量,用于通信的socket
//两个线程用于接收和发送信息
void sendFun();//发送信息线程
void recvFun();//接收信息线程
int main()
{
    HANDLE h1, h2;//线程句柄,其实就是一串数字用来标识线程对象
    SOCKADDR_IN addr;
    int info;
    WSADATA data;
    WORD version;
    //设定版本,与初始化
    version = MAKEWORD(2, 2);
    info = WSAStartup(version, &data);
    if (info != 0)
    {
        printf("初始化失败\n");
        return 0;
    }
    if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2)
    {
        printf("加载失败\n");
        WSACleanup();
        return 0;
    }
    sockClient = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
                                                   //要连接的服务器的ip,因为现在服务器端就是本机,所以写本机ip
                                                   //127.0.0.1一个特殊的IP地址,表示是本机的IP地址
    addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    //端口要与服务器相同,不然找不到
    addr.sin_port = htons(0606);
    //用IPV4地址
    addr.sin_family = AF_INET;
    //主动连接服务器
    while(1){
        if(connect(sockClient, (SOCKADDR*)&addr, sizeof(SOCKADDR))==0)
            break;
        else
            ;
    }

    //创建线程后立即执行
    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);//会阻塞,直到线程运行结束
    WaitForSingleObject(h2, INFINITE);
    shutdown(sockClient,2);
    closesocket(sockClient);//关闭套接字
    return 0;
}

void sendFun()
{
    char buf[128];
    while (1)
    {
        printf("客户端的信息: ");
        scanf("%s", buf);
        //发送数据
        send(sockClient, buf, strlen(buf) + 1, 0);
    }
}


void recvFun()
{
    char buf[128];
    //接收服务发送的数据
    while (1)
    {
        int n;
        if(recv(sockClient, buf, 128, 0)>0){
            printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
            printf("%s%s\n","服务器端的信息: ", buf);
            printf("客户端的信息:");
        }
        else{
            break;
        }
    }
}

四、实验结果

1. 服务器端

服务器端

2. 客户端

在这里插入图片描述

  • 19
    点赞
  • 236
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值