计网实验(三):基本Winsock编程

git地址:https://github.com/MyUsernameIsJX/WinSock

目录

1. 实验目的及环境

2. 实验要求

3. 实验内容及结果

4. 实验中遇到的问题以及解决方法

5. 实验总结


1. 实验目的及环境

1、了解Winsock编程原理;

2、熟悉Windows网络编程接口;

Visual C或 C、VB等。

2. 实验要求

编写一个C/S通讯程序。

具体要求:

  1. 使用WINSOCK 通讯:WINSOCKWindows Sockets API的简称,已经成为Windows广泛应用的、开放的、支持多种协议、事实上的网络编程接口标准。
  2. 客户方程序与服务方程序位于两台不同的机器上,在客户方通过指定服务方的IP地址和端口号来通讯;
  3. 服务器程序,始终处于监听状态,具有连续接收客户发送的信息的能力(发送的信息任意)。
  4. 至少要保证2个客户端之间可以相互通信,注意界面设计的合理性,操作的友好性

3. 实验内容及结果

实验选择的是C#脚本语言

界面设计:

Server:

计网实验(三):基本Winsock编程_第1张图片

Client:

计网实验(三):基本Winsock编程_第2张图片

代码部分:

服务器代码

全局变量定义:

        //服务器端的socket
        private Socket serverSocket = null;  
         //服务器端线程
        private Thread serverThread = null; 
        //保存已连接的客户端socket
        private List clientSocketList = null;
        //保存已开启的接收数据的客户端线程
        private List clientThreadList = null;
        // 接受数据缓冲区
        private byte[] result = new byte[1024];
        // 跨线程更新UI
        Action AsyncUIDelegate;
        // 服务器IP
        private string IP = "";
        // 服务器开放的端口
        private int Port;
        // 最大用户连接数
        private int MAXCLIENTNUM = 5;
        // 当前用户连接数
        private int iClientNum = 0;

初始化控件:

            // 将本机所有可用ip加入下拉框内
            String[] ipAddress = getLocalAddress();
            for(int i = 0;i

addMonitorMessage():

        //向聊天框添加信息
        private void addMonitorMessage(String message)
        {
            if("".Equals(tbMonitor.Text))
            {
                tbMonitor.Text = message + "\r\n";
            }
            else
            {
                tbMonitor.Text += message + "\r\n";
            }
            return;
        }

getLocakAddress():

        // 获得本机所有ip地址
        private String[] getLocalAddress()
        {
            // 获得主机名
            String hostName = Dns.GetHostName();

            // 根据主机名查找ip
            IPHostEntry iPHostEntry = Dns.GetHostEntry(hostName);
            String[] result = new String[iPHostEntry.AddressList.Length];
            int i = 0;
            foreach (IPAddress iPAddress in iPHostEntry.AddressList)
            {
                result[i] = iPHostEntry.AddressList[i].ToString();
                i++;
            }
            return result;

        }

创建服务器:

	    // 启动服务器
        private void btnStart_Click(object sender, EventArgs e)
        {
            addMonitorMessage("正在开启服务器......");
            startServer();
            btnStart.Enabled = false;
            btnStop.Enabled = true;
        }
        private void startServer()
        {
            // 初始化客户端socket管理列表
            clientSocketList = new List();
            // 初始化客户端线程管理列表
            clientThreadList = new List();
            IP = cboIP.SelectedItem.ToString();
            Port = Int32.Parse(tbPort.Text.ToString());
            IPAddress iPAddress = IPAddress.Parse(IP);
            serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            IPEndPoint pEndPoint = new IPEndPoint(iPAddress, Port);
            serverSocket.Bind(pEndPoint);  
            serverSocket.Listen(10);
            serverThread = new Thread(LitenerClientConnect);
            serverThread.Start();
            tbMonitor.Invoke(AsyncUIDelegate, "服务器启动成功!");
        }

监听客户端的连接:

        // 监听客户端的链接
        private void LitenerClientConnect()
         {
            while(true)
            {
                Socket clientSocket = serverSocket.Accept();
                // 将接受到的客户端socket添加到clientSocketList中
                clientSocketList.Add(clientSocket);
                // 获取客户端的IP和端口号
                IPEndPoint clientIpEndPoint = clientSocket.RemoteEndPoint as IPEndPoint;
                IPAddress clientIP = clientIpEndPoint.Address;
                int clientPort = clientIpEndPoint.Port; 
                tbMonitor.Invoke(AsyncUIDelegate, "IP:" + clientIP.ToString()+"\t
                        端口:" + clientPort.ToString() +"\t已成功连接到服务器.");
                // 向客户端发送信息
                // 使用UTF8编码
                clientSocket.Send(Encoding.UTF8.GetBytes("\t连接成功!\t本机IP:" + 
                        clientIP.ToString() + "\t\t端口:" + clientPort.ToString()));
                Thread receiverMessage = new Thread(receiverData);
                clientThreadList.Add(receiverMessage);
                receiverMessage.Start(clientSocket);
                iClientNum++;
            }
        }    

接受客户端的消息:

         // 接受客户端的消息
        private void receiverData(Object clientSocket)
        {
            Socket myClientSocket = (Socket)clientSocket; 
            while(true)
            {
                try // 防止服务器端接受消息堵塞
                {
                    int receiverNum = myClientSocket.Receive(result);
                    string message = Encoding.ASCII.GetString(result, 0, receiverNum);
                    // 判断收到EXIT,如果是空则是客户端断开连接
                    if ("EXIT".Equals(message))
                    {
                        clientSocketList.Remove(myClientSocket);
                        iClientNum--;
                        IPEndPoint clientIpEndPoint = 
                               myClientSocket.RemoteEndPoint as IPEndPoint;
                        IPAddress clientIP = clientIpEndPoint.Address;
                        int clientPort = clientIpEndPoint.Port;
                        tbMonitor.Invoke(AsyncUIDelegate, "IP:" + clientIP.ToString() + 
                               "\t端口:" + clientPort.ToString() +"\t已断开连接."); 
                        break;
                    }
                    else
                    {
                        for (int i = 0; i < clientSocketList.Count; i++)
                        {
                            clientSocketList[i].Send(Encoding.UTF8.GetBytes(message));
                        }
                        tbMonitor.Invoke(AsyncUIDelegate, message);
                    }
                }
                catch(Exception)
                {
                    myClientSocket.Close();
                    break;
                }
            }
        }

关闭服务器

        private void btnStop_Click(object sender, EventArgs e)
        {
            addMonitorMessage("正在关闭服务器......");
            stopServer();
            btnStart.Enabled = true;
            btnStop.Enabled = false;
        } 
        // 关闭服务器
        private void stopServer()
        {
            serverSocket.Close();
            serverThread.Abort();
            for (int i = 0; i < clientSocketList.Count; i++)
            {
// 先检查连接到的客户端是否仍在连接,然后断开正在连接的客户端
                if(clientSocketList[i].Connected == true)
                {
                    clientSocketList[i].Disconnect(true);
                }
                clientSocketList.Remove(clientSocketList[i]);
            }
			// 关闭所有客户端线程
            for (int i = 0; i < clientThreadList.Count; i++)
            {
                clientThreadList[i].Abort();
            }
            tbMonitor.Invoke(AsyncUIDelegate, "服务器关闭!");
            btnStart.Enabled = true;
            btnStop.Enabled = false;
        }

获取GUI中的IPPort

        private void tbPort_TextChanged(object sender, EventArgs e)
        {
            Port = Int32.Parse(tbPort.Text.ToString());
        }
        private void cboIP_SelectedIndexChanged(object sender, EventArgs e)
        {
            IP = cboIP.SelectedItem.ToString();
        }

添加新的客户端form

        private void btnAddClient_Click(object sender, EventArgs e)
        {
            if (iClientNum == MAXCLIENTNUM)
            {
                MessageBox.Show("用户数已达最大值,无法创建新的客户机!");
                return;
            }
            Form clientForm = new Form2();
            clientForm.Show();
        }

客户端代码

所有全局变量以及含义:

        // 客户端接收服务器消息线程
        private Thread threadClient = null;
        // 服务器socket
        private Socket socketClient = null;
        // 客户名字
        private String clientName = "";
        // 客户端端口
        private int clientPort;
        // 客户端IP
        private IPAddress clientIP = null;
        // 客户端IPEndPoint
        private IPEndPoint clientIpEndPoint = null;
        // 消息缓存区
        byte[] recvBuffer = null;

初始化控件:

        private void initView()
        {
            btnConnectToServer.Enabled = true;
            btnExit.Enabled = false;
            tbMonitor.ReadOnly = true;
            tbIP.ForeColor = Color.Red;
            tbPort.ForeColor = Color.Red;
            tbName.ForeColor = Color.Red;
        }

连接服务器:

        // 连接服务器
        private void btnConnectToServer_Click(object sender, EventArgs e)
        {
            try // 防止输入的信息出现错误  eg:port中含有字母
            {
                clientIP = IPAddress.Parse(tbIP.Text.ToString().Trim());
                clientPort = Int32.Parse(tbPort.Text.ToString().Trim());
                clientName = tbName.Text.ToString().Trim();
            }
            catch(Exception)
            {
                MessageBox.Show("输入信息不合法,请重新输入!");
                return;
            }
            btnConnectToServer.Enabled = false;
            btnExit.Enabled = true;
            clientIpEndPoint = new IPEndPoint(clientIP, clientPort);
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            tbIP.ReadOnly = true;   
            tbPort.ReadOnly = true; 
            tbName.ReadOnly = true; 
            try
            {
                socketClient.Connect(clientIpEndPoint);
            }
            catch
            {
                MessageBox.Show("连接失败\r\n");
                btnConnectToServer.Enabled = true;
                btnExit.Enabled = false;
                return;
            }
            threadClient = new Thread(recvMsg);
            threadClient.Start();
        }

接受服务器发来的消息:

        // 接受服务器端发来的消息
        private void recvMsg()
        {
            while(true)
            {
                try  // 
                {
                    // 临时存储接收到的信息  1kb
                    recvBuffer = new byte[1024];
                    //将客户端套接字接收到的数据存入内存缓冲区,并获取长度
                    int length = socketClient.Receive(recvBuffer);
                    string strRecvMsg = Encoding.UTF8.GetString(recvBuffer, 0, length);
                    addMonitorMessage(strRecvMsg);
                }
                catch
                {
                    addMonitorMessage("远程服务器已经中断连接\r\n");
                    btnConnectToServer.Enabled = true;
                    break;
                }
            }
        }

客户端发送消息:

        private void btnCSend_Click(object sender, EventArgs e)
        {
            try
            {
                string strMsg = tbSendMsg.Text.ToString().Trim();
                if("".Equals(strMsg))
                {
                    MessageBox.Show("消息不能为空!");
                    return;
                }
                if(btnConnectToServer.Enabled == true)
                {
                    MessageBox.Show("请先连接到服务器,再发送消息");
                    return;
                }
                tbSendMsg.Text = "";
                strMsg = "[" + clientName + "]:" + strMsg;
                socketClient.Send(Encoding.UTF8.GetBytes(strMsg));
            }
            catch
            {
                MessageBox.Show("消息发送失败,请检查是否连接到服务器");
                return;
            }
        }

断开连接:

        private void btnExit_Click(object sender, EventArgs e)
        {
            try
            {
                // 通知服务器即将断开连接
                socketClient.Send(Encoding.UTF8.GetBytes("EXIT"));

                socketClient.Dispose();
                socketClient.Close();
                btnExit.Enabled = false;
                btnConnectToServer.Enabled = true;
                tbName.ReadOnly = false;
            }
            catch
            {
                MessageBox.Show("客户机关闭失败!");
                return;
            }
        }

     实验结果

服务器的初始化和启动:

计网实验(三):基本Winsock编程_第3张图片

添加客户以及连接服务器:

计网实验(三):基本Winsock编程_第4张图片

服务器发送消息:

计网实验(三):基本Winsock编程_第5张图片

客户端发送消息:

计网实验(三):基本Winsock编程_第6张图片

关闭客户端:

计网实验(三):基本Winsock编程_第7张图片

关闭服务器:

计网实验(三):基本Winsock编程_第8张图片

4. 实验中遇到的问题以及解决方法

问题:

多线程程序中,新创建的线程不能访问UI线程创建的窗口控件,访问窗口的控件时无法对其控制。

解决方法:

将窗口构造函数中的CheckForIllegalCrossThreadCalls设置为false;然后就能安全的访问窗体控件。

System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;

问题:

服务器创建时输入的ip可能不是本机的ip

解决方法:

       将ip输入框改为下拉框,内容是本机所有可用ipv4的ip

问题:

传送消息之后,收到的中文显示乱码

解决方法:

       发送消息时使用UTF8编码

clientSocketList[i].Send(Encoding.UTF8.GetBytes(message));

5. 实验总结

在调试的过程中,巩固提高了针对问题、错误的分析能力和逻辑思维能力。

学会了网络编程的一些基本知识。

你可能感兴趣的:(计算机网络)