入门级C# Socket编程实现

之前因为忙一直没时间关注博客,非常抱歉没能及时回答到大家的问题,针对大家遇到的一些问题我对推文里的socket服务器端和客户端进行了一些改进。
改动如下(如果是第一次看这篇博客的话可以直接忽略这段文字):

  1. 针对多个客户端连接服务器端会出现客户端被覆盖问题:使用了List来存放连接到服务器的客户端,并将客户端信息(ip和端口号)不同的客户端ip可能一样但是端口号不一样,放进comBox组件里面,可以在comBox里面选择想要发送信息的客户端。
    【这里补充个知识点:在服务器端使用listen(value)来监听客户端,value值是想要连接的客户端数量,如果没有使用accept()方法的话,value值是多少,服务器端就最多只能连接上value个客户端;如果使用了accept()方法的话,客户端队列会被存放在其他地方,listen(value)中的value值就会失去作用,服务器可以连接超过value值的客户端】
  2. 针对发送中文乱码问题:使用Encoding.UTF8.GetBytes()进行编码,相应的使用Encoding.UTF8.GetString()进行解码。
  3. 对UI进行了小改动,服务器端添加了客户端列表,添加了接收框清除按钮。
  4. 代码都进行了更新。

说明:本篇推文侧重讲解C#的Socket编程实现,里面有完整实现的GIF动图,大家可以先去看一下,Socket原理介绍的不多,可能有很多不足的地方,原理方面大家可以去找其他资料看一下。
Socket编程这部分我主要着重介绍了Socket编程用到的一些相关类、方法以及实现步骤,不断地分步骤介绍,是为了能够加深印象也方便大家理解并复刻这款简单软件,大家学会了可以自己去拓展功能或者嵌入其他项目中,socket相关类和方法可能会有点冗余,大家可以直接跳到完整的实现代码去研究学习。

一、Socket套接字

Socket的定义

套接字(Socket),是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口,Socket所处的位置大概是下面这样的。
在这里插入图片描述
五层架构使用的协议
我们可以将Socket联想成是由两个Socket对象搭建的成的一根通信管道,管道的两端是这两个Socket对象,而这根管道的连接的是两台主机的应用进程。假设A、B两台主机上的应用进程要互相发送数据,那么我们就可以用Socket打造一条连接A、B主机进程的管道,A主机进程使用Socket对象把数据往管道里面一丢,然后B主机进程就可以使用Socket对象把数据给拿出来,反之当B主机进程要给A主机进程发送数据的时候也是通过操作Socket对象就可以简单实现数据的发送和接收。
在这里插入图片描述
学过计算机网络的同学都知道数据在因特网上传输并不像上面说的那么简单,根据经典的五层协议体系结构(如下图),一个报文从一个进程传输到另一个进程需要经过自上到下层层协议的封装,然后转换成比特流通过物理层,途径多个路由,最后才能到到达目的主机【这里要用到的IP来标定网络上特定的主机】,然后再在目的主机上经过层层解析最终根据端口号把报文传输到目的进程,而使用Socket就不需要我们自己层层封装信息,也不用管信息是如何在网络上传输的,我们只需要绑定IP地址和应用进程号,使用Socket对象就可以实现进程之间数据的发送和接收,大大缩减了我们的工作,非常的nice。
这里推荐一个计算机网络课程,图文并茂讲计算机网络巨通俗易懂
https://www.bilibili.com/video/BV1c4411d7jb?share_source=copy_web
计算机网络体系

二、Socket编程

通过上面的简单介绍,我们能大致了解Socket的作用是完成两个应用程序之间的数据传输,我们只要知道要通信的两台主机的IP地址和进程的端口号,然后可以用Socket让这两个进程进行通信,接下来我们就进入正题,使用C#进行Socket编程,完成本地两个进程的通信

1.效果展示

在这里插入图片描述

2.Socket通信基本流程图

在这里插入图片描述
根据上面的流程图可以知道使用socket实现通信大致需要完成以下几个步骤:
服务器端:
第一步:建立一个用于通信的Socket对象
第二步:使用bind绑定IP地址和端口号
第三步:使用listen监听客户端
第四步:使用accept中断程序直到连接上客户端
第五步:接收来自客户端的请求
第六步:返回客户端需要的数据
第七步:如果接收到客户端已关闭连接信息就关闭服务器端

客户端:
第一步:建立一个用于通信的Socket对象
第二步:根据指定的IP和端口connet服务器
第三步:连接成功后向服务器端发送数据请求
第四步:接收服务器返回的请求数据
第五步:如果还需要请求数据继续发送请求
第六步:如果不需要请求数据就关闭客户端并给服务器发送关闭连接信息

3.Socket编程常用类和方法

相关类

(1) IPAddress:包含了一个IP地址[提供 Internet 协议 (IP) 地址]

//这里的IP是long类型的,这里使用Parse()可以将string类型数据转成IPAddress所需要的数据类型
IPAddress IP = IPAddress.Parse();

(2) IPEndPoint:包含了一对IP地址和端口号

/*public IPEndPoint(IPAddress address, int port);*/
IPEndPoint endPoint = new IPEndPoint(ip,port);	//处理IP地址和端口的封装类

(3)Encoding.ASCII:编码转换

Encoding.ASCII.GetBytes()	//将字符串转成字节
Encoding.ASCII.GetString()	//将字节转成字符串

(4)获取当前时间

DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ")

Socket编程函数【作用+示例】:

(1)Socket()
  • 创建Socket对象,构造函数需要输入三个参数,创建客户端和服务器端Socket对象示例如下
Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Socket构造函数参数

  • AddressFamily指定Socket用来解析地址的寻址方案
  • SocketType定义要打开的Socket的类型
  • ProtocolType:向Windows Sockets API通知所请求的协议
(2)Bind()
  • 绑定一个本地的IP和端口号,参数是一个绑定了IP和端口号的IPEndPoint对象
ServerSocket.Bind(new IPEndPoint(ip,port));
或者
IPEndPoint ipEndPoint = new IPEndPoint(ip,port)
ServerSocket.Bind(ipEndPoint);
(3)Listen()
  • 让Socket侦听传入的连接,参数为指定侦听队列的容量,
ServerSocket.Listen(10);
(4)Connect()
  • 建立与远程主机的连接
ClientSocket.Connect(ipEndPoint);
(5)Accept()
  • 接收连接并返回一个新的Socket,Accept会中断程序,直到有客户端连接
Socket socket = ServerSocket.Accept();
(6)Send()
  • 输出数据到Socket
//Encoding.ASCII.GetBytes()将字符串转成字节
//byte[] message = Encoding.ASCII.GetBytes("Connect the Server"); //通信时实际发送的是字节数组,所以要将发送消息转换字节
byte[] message = Encoding.UTF8.GetBytes("Connect the Server");	//防止中文乱码使用该方法对字符串进行编码
ClientSocket.Send(message);
socket.Send(message);
(7)Receive()
  • 从Socket中读取数据
byte[] receive = new byte[1024];
int length = ClientSocket.Receive(receive); // length 接收字节数组长度
int length = socket.Receive(receive);
(8)Close()
  • 关闭Socket,销毁连接
socket.Close()
ClientSocket.Close()
类型函数
服务器socketBind()
Listen()
Accept()
客户端socketConnect()
公共socketReceive()
Send()
Close()

4.编程实现步骤

(1)UI设计

服务器端:
在这里插入图片描述

客户端:
在这里插入图片描述

下面是我给各组件设置的name值,客户端和服务器端除了Button有一个不一样其他组件设置的name值都是一样的

组件Name
TextBoxtextBox_Addr
textBox_Port
RichTextBoxrichTextBox_Receive
richTextBox_Send
Buttonbutton_Accpet
button_Connect
button_Close
button_Send
ComBoxcomBox_Clients

(2)服务器端:

实现步骤:
第一步:创建一个用于监听连接的Socket对象;

Socket ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于监听连接的套接字

第二步:用指定的端口号和服务器的ip建立一个IPEndPoint对象;

IPAddress IP = IPAddress.Parse(textBox_Addr.Text);	//获取输入的IP地址
int Port = int.Parse(textBox_Port.Text);	//获取输入的端口号
//IPAddress ip = IPAddress.Any;
IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);	

第三步:用socket对象的Bind()方法绑定IPEndPoint;

ServerSocket.Bind(iPEndPoint);

第四步:用socket对象的Listen()方法开始监听;

ServerSocket.Listen(10);

第五步:建立与客户端的连接,用socket对象的Accept()方法创建用于和客户端进行通信的socket对象;(因为accept会中断程序,所以我使用线程来实现客户端的接入)

Socket socket = socketAccept.Accept();

第六步:接收来自客户端的信息(我是使用线程来实现数据的接收)

byte[] recieve = new byte[1024];
int len = socket.Receive(recieve);
richTextBox_Recieve.Text += Encoding.UTF8.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串

第七步:向客户端发送信息

byte[] send = new byte[1024];
send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
socket.Send(send);  //调用Send()向客户端发送数据

第八步:关闭Socket

socket.Close();     //关闭用于通信的套接字          
ServerSocket.Close();   //关闭用于连接的套接字
socketAccept.Close();   //关闭与客户端绑定的套接字
th1.Abort();    //关闭线程1

具体实现代码:
(1)因为有使用线程,为了防止出错,在初始化的时候关闭检测

private void Form1_Load(object sender, EventArgs e)
        {
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

(2)建立连接,首先创建一个ServerSocket将连接需要的参数都设置好并监听连接,然后创建线程Accept客户的连接

 		/*****************************************************************/
        #region 连接客户端(绑定按钮事件)
        private void button_Accpet_Click(object sender, EventArgs e)
        {

            ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于通信的套接字
            
            richTextBox_Receive.Text += "正在连接...\n";
            button_Accpet.Enabled = false;  //禁止操作接收按钮
           
            //1.绑定IP和Port
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);
            int Port = int.Parse(textBox_Port.Text);

            //IPAddress ip = IPAddress.Any;
            IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);

            try
            {
                //2.使用Bind()进行绑定
                ServerSocket.Bind(iPEndPoint);
                //3.开启监听
                //Listen(int backlog); backlog:监听数量 
                ServerSocket.Listen(10);

                /*
                 * tip:
                 * Accept会阻碍主线程的运行,一直在等待客户端的请求,
                 * 客户端如果不接入,它就会一直在这里等着,主线程卡死
                 * 所以开启一个新线程接收客户单请求
                 */

                //开启线程Accept进行通信的客户端socket
                th1 = new Thread(Listen);   //线程绑定Listen函数
                th1.IsBackground = true;    //运行线程在后台执行
                th1.Start(ServerSocket);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
                Console.WriteLine("1");
            }
            catch
            {
                MessageBox.Show("服务器出问题了");
            }
        }
        #endregion
        /*****************************************************************/

		/*****************************************************************/
        #region 建立与客户端的连接
        void Listen(Object sk) 
        {          
            try
            {
               while (true) 
               {
                    //GNFlag如果为1就进行中断连接,使用在标志为是为了保证能够顺利关闭服务器端
                    /*
                     * 当客户端关闭一次后,如果没有这个标志位会使得服务器端一直卡在中断的位置
                     * 如果服务器端一直卡在中断的位置就不能顺利关闭服务器端
                     */

                    //4.阻塞到有client连接
                    Socket Client = ServerSocket.Accept(); //声明用于与某一个客户端通信的套接字
                    socketsList.Add(Client);               //将连接的客户端存进List
                    
                    //获取客户端信息将不同客户端并存进comBox
                    string client = Client.RemoteEndPoint.ToString();
                    comboBox_Clients.Items.Add(client);
                    
                    CFlag = 0;  //连接成功,将客户端关闭标志设置为0
                    SFlag = 1;  //当连接成功,将连接成功标志设置为1

                    richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + client + "连接成功";
                    richTextBox_Receive.Text += "\r\n";

                    //开启第二个线程接收客户端数据
                    th2 = new Thread(Receive);  //线程绑定Receive函数
                    th2.IsBackground = true;    //运行线程在后台执行
                    th2.Start(Client);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
               }
            }
            catch 
            {
                //MessageBox.Show("没有连接上客户端");   
            }
        }
        #endregion
        /*****************************************************************/

(3)连接上客户端后,使用线程接收客户端数据,参数是Accept到的Socket对象

/*****************************************************************/
        #region 接收客户端数据
        void Receive(Object sk)
        {
            Socket socket = sk as Socket;  //创建用于通信的套接字(这里是线程传过来的client套接字)

            while (true)
            {
                try
                {
                    if (CFlag == 0 && SFlag == 1)
                    {
                        //5.接收数据
                        byte[] recieve = new byte[1024];
                        int len = socket.Receive(recieve);

                        //6.打印接收数据
                        if (recieve.Length > 0)
                        {
                            //如果接收到客户端停止的标志
                            if (Encoding.UTF8.GetString(recieve, 0, len) == "*close*")
                            {
                                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "客户端已退出" + "\n";
                                CFlag = 1;      //将客户端关闭标志设置为1

                                break;      //退出循环
                            }

                            //打印接收数据
                            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                            richTextBox_Receive.Text += "\r\n";
                            richTextBox_Receive.Text += "接收" + socket.RemoteEndPoint.ToString() + ":";
                            //richTextBox_Receive.Text += Encoding.ASCII.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串
                            richTextBox_Receive.Text += Encoding.UTF8.GetString(recieve, 0, len);   //接收中文不会乱码
                            
                        }
                    }
                    else
                    {
                        break;  //跳出循环
                    } 
                }
                catch
                {
                    MessageBox.Show("收不到信息");
                }  
            }  
        }
        #endregion
/*****************************************************************/

(4)向客户端发送数据

        /*****************************************************************/
        #region 向客户端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
            //SFlag标志连接成功,同时当客户端是打开的时候才能发送数据
            if(SFlag == 1 && CFlag == 0)
            {
                byte[] send = new byte[1024];
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
                socket.Send(send);  //调用Send()向客户端发送数据

                //打印发送时间和发送的数据
                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";
                richTextBox_Receive.Text += richTextBox_Send.Text + "\n";
                richTextBox_Send.Clear();   //清除发送框
            } 
        }
        #endregion
        /*****************************************************************/

(5)关闭服务器端

/*****************************************************************/
        #region 关闭服务器端
        private void button_Close_Click(object sender, EventArgs e)
        {
            //若连接上了客户端需要关闭线程2和socket,没连接上客户端直接关闭线程1和其他套接字
            if(CFlag == 1)
            {
                th2.Abort();        //关闭线程2
                socket.Close();     //关闭用于通信的套接字
            }
            
            ServerSocket.Close();   //关闭用于连接的套接字
            socketAccept.Close();   //关闭与客户端绑定的套接字
            th1.Abort();    //关闭线程1

            CFlag = 0;  //将客户端标志重新设置为0,在进行连接时表示是打开的状态
            SFlag = 0;  //将连接成功标志程序设置为0,表示退出连接
            button_Accpet.Enabled = true;
            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
            richTextBox_Receive.Text += "服务器已关闭" + "\n";
            MessageBox.Show("服务器已关闭");
        }
        #endregion
        /*****************************************************************/

(3)客户端:

第一步:建立一个Socket对象;

Socket ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字

第二步:用指定的端口号和服务器的ip建立一个IPEndPoint对象;

IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

第三步:用socket对像的Connect()方法以上面建立的EndPoint对象做为参数,向服务器发出连接请求;

ClientSocket.Connect(iPEndPoint);

第四步:如果连接成功,就用socket对象的Send()方法向服务器发送信息;

byte[] send = new byte[1024];
send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
ClientSocket.Send(send);    //调用Send()函数发送数据

第五步:用socket对象的Receive()方法接受服务器的信息 ;

byte[] receive = new byte[1024];
ClientSocket.Receive(receive);  //调用Receive()接收字节数据

第六步:通信结束后关闭socket;

ClientSocket.Close();   //关闭套接字

具体代码实现
(1)使用线程,为了防止出错,在初始化的时候关闭检测

private void Form1_Load(object sender, EventArgs e)
{
	Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
}

(2)建立与服务器端的连接

/*****************************************************************/
        #region 连接服务器端
        private void button_Connect_Click(object sender, EventArgs e)
        {
            ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字
            
            richTextBox_Recieve.Text += "正在连接...\n";
            
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
            int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
            IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

            try
            {  
                ClientSocket.Connect(iPEndPoint);       //用socket对象的Connect()方法以上面建立的IPEndPoint对象做为参数,向服务器发出连接请求
                SFlag = 1;  //若连接成功将标志设置为1
                    
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + textBox_Addr.Text + "连接成功" + "\n";
                button_Connect.Enabled = false;     //禁止操作连接按钮
                
                //开启一个线程接收数据
                th1 = new Thread(Receive);
                th1.IsBackground = true;
                th1.Start(ClientSocket);    
            }
            catch 
            {
                MessageBox.Show("服务器未打开");
            }    
        }
        #endregion
        /*****************************************************************/

(3)若连接成功,创建线程实现接收服务器端的数据

/*****************************************************************/
        #region 接收服务器端数据
        void Receive(Object sk)
        {
            Socket socketRec = sk as Socket;

            while (true)
            {
                //5.接收数据
                byte[] receive = new byte[1024];
                ClientSocket.Receive(receive);  //调用Receive()接收字节数据

                //6.打印接收数据
                if (receive.Length > 0)
                {
                    richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "接收:";   //打印接收时间
                    richTextBox_Recieve.Text += Encoding.UTF8.GetString(receive);  //将字节数据根据ASCII码转成字符串
                    richTextBox_Recieve.Text += "\r\n";
                }
            }
        }
        #endregion
        /*****************************************************************/

(4)向服务器端发送数据(于send按钮进行事件绑定)

/*****************************************************************/
        #region 向服务器端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
            //只有连接成功了才能发送数据,接收部分因为是连接成功才开启线程,所以不需要使用标志
            if (SFlag == 1)
            {
                byte[] send = new byte[1024];
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
                ClientSocket.Send(send);    //调用Send()函数发送数据

                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";   //打印发送数据的时间
                richTextBox_Recieve.Text += richTextBox_Send.Text + "\n";   //打印发送的数据
                richTextBox_Send.Clear();   //清空发送框
            }
        }
        #endregion
        /*****************************************************************/

(5)关闭客户端

		/*****************************************************************/
        #region 关闭客户端
        private void buttonClose_Click(object sender, EventArgs e)
        {
            //保证是在连接状态下退出
            if (SFlag == 1)
            {
                byte[] send = new byte[1024];
                send = Encoding.ASCII.GetBytes("*close*");  //关闭客户端时给服务器发送一个退出标志
                ClientSocket.Send(send);
                
                th1.Abort();    //关闭线程
                ClientSocket.Close();   //关闭套接字
                
                button_Connect.Enabled = true;  //允许操作按钮
                SFlag = 0;  //客户端退出后将连接成功标志程序设置为0
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                richTextBox_Recieve.Text += "客户端已关闭" + "\n";
                MessageBox.Show("已关闭连接");
            }
        }
        #endregion
        /*****************************************************************/

五、服务器端完整代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Server
{
    public partial class Form1 : Form
    {
        //这里声明多个套接字是为了在连接,接收数据,发送数据的函数中不发生混乱,同时方便关闭
        public  Socket ServerSocket;    //声明用于监听的套接字
        public static List<Socket> socketsList = new List<Socket>();    //创建一个全局的List用来存放不同的Client套接字

        public static int SFlag = 0;    //连接成功标志
        public static int CFlag = 0;    //客户端关闭的标志

        Thread th1;     //声明线程1
        Thread th2;     //声明线程2

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //comboBox_Clients.SelectedIndex = 0;
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

        /*****************************************************************/
        #region 连接客户端
        private void button_Accpet_Click(object sender, EventArgs e)
        {

            ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //创建用于通信的套接字
            
            richTextBox_Receive.Text += "正在连接...\n";
            button_Accpet.Enabled = false;  //禁止操作接收按钮
           
            //1.绑定IP和Port
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);
            int Port = int.Parse(textBox_Port.Text);

            //IPAddress ip = IPAddress.Any;
            IPEndPoint iPEndPoint = new IPEndPoint(IP, Port);

            try
            {
                //2.使用Bind()进行绑定
                ServerSocket.Bind(iPEndPoint);
                //3.开启监听
                //Listen(int backlog); backlog:监听数量 
                ServerSocket.Listen(10);

                /*
                 * tip:
                 * Accept会阻碍主线程的运行,一直在等待客户端的请求,
                 * 客户端如果不接入,它就会一直在这里等着,主线程卡死
                 * 所以开启一个新线程接收客户单请求
                 */

                //开启线程Accept进行通信的客户端socket
                th1 = new Thread(Listen);   //线程绑定Listen函数
                th1.IsBackground = true;    //运行线程在后台执行
                th1.Start(ServerSocket);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
                Console.WriteLine("1");
            }
            catch
            {
                MessageBox.Show("服务器出问题了");
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 建立与客户端的连接
        void Listen(Object sk) 
        {          
            try
            {
               while (true) 
               {
                    //GNFlag如果为1就进行中断连接,使用在标志为是为了保证能够顺利关闭服务器端
                    /*
                     * 当客户端关闭一次后,如果没有这个标志位会使得服务器端一直卡在中断的位置
                     * 如果服务器端一直卡在中断的位置就不能顺利关闭服务器端
                     */

                    //4.阻塞到有client连接
                    Socket Client = ServerSocket.Accept(); //声明用于与某一个客户端通信的套接字
                    socketsList.Add(Client);               //将连接的客户端存进List
                    
                    //获取客户端信息将不同客户端并存进comBox
                    string client = Client.RemoteEndPoint.ToString();
                    comboBox_Clients.Items.Add(client);
                    comboBox_Clients.SelectedIndex = 0;
                    
                    CFlag = 0;  //连接成功,将客户端关闭标志设置为0
                    SFlag = 1;  //当连接成功,将连接成功标志设置为1

                    richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + client + "连接成功";
                    richTextBox_Receive.Text += "\r\n";

                    //开启第二个线程接收客户端数据
                    th2 = new Thread(Receive);  //线程绑定Receive函数
                    th2.IsBackground = true;    //运行线程在后台执行
                    th2.Start(Client);    //Start里面的参数是Listen函数所需要的参数,这里传送的是用于通信的Socket对象
               }
            }
            catch 
            {
                //MessageBox.Show("没有连接上客户端");   
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 接收客户端数据
        void Receive(Object sk)
        {
            Socket socket = sk as Socket;  //创建用于通信的套接字(这里是线程传过来的client套接字)

            while (true)
            {
                try
                {
                    if (CFlag == 0 && SFlag == 1)
                    {
                        //5.接收数据
                        byte[] recieve = new byte[1024];
                        int len = socket.Receive(recieve);

                        //6.打印接收数据
                        if (recieve.Length > 0)
                        {
                            //如果接收到客户端停止的标志
                            if (Encoding.UTF8.GetString(recieve, 0, len) == "*close*")
                            {
                                richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "客户端已退出" + "\n";
                                CFlag = 1;      //将客户端关闭标志设置为1

                                break;      //退出循环
                            }

                            //打印接收数据
                            richTextBox_Receive.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                            richTextBox_Receive.Text += "接收" + socket.RemoteEndPoint.ToString() + ":";
                            //richTextBox_Receive.Text += Encoding.ASCII.GetString(recieve, 0, len);  //将字节数据根据ASCII码转成字符串
                            richTextBox_Receive.Text += Encoding.UTF8.GetString(recieve, 0, len);   //接收中文不会乱码
                            richTextBox_Receive.Text += "\r\n";
                        }
                    }
                    else
                    {
                        break;  //跳出循环
                    } 
                }
                catch
                {
                    MessageBox.Show("收不到信息");
                }  
            }  
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 向客户端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
            //SFlag标志连接成功,同时当客户端是打开的时候才能发送数据
            if(SFlag == 1 && CFlag == 0)
            {
                byte[] send = new byte[1024];
                //send = Encoding.ASCII.GetBytes(richTextBox_Send.Text);    //将字符串转成字节格式发送
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text);
                /*
                 * 上面将每一个连接的client的套接字信息(ip和端口号)存放进了combox
                 * 我们可以在combox中选择需要通信的客户端
                 * 通过comboBox_Clients.SelectedIndex获取选择的index,此index对于List中的socket对象
                 * 从而实现对选择的客户端发送信息
                 */
                int i = comboBox_Clients.SelectedIndex;
                string client = comboBox_Clients.Text;
                socketsList[i].Send(send); //调用Send()向客户端发送数据

                //打印发送时间和发送的数据
                richTextBox_Receive.Text += "*" +  DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "向" + client +  "发送:";
                richTextBox_Receive.Text += richTextBox_Send.Text + "\n";
                richTextBox_Send.Clear();   //清除发送框
            } 
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 关闭服务器端
        private void button_Close_Click(object sender, EventArgs e)
        {
            //若连接上了客户端需要关闭线程2和socket,没连接上客户端直接关闭线程1和其他套接字
            if(CFlag == 1)
            {
                th2.Abort();        //关闭线程2
                foreach(Socket s in socketsList)
                    s.Close();     //关闭用于通信的套接字
            }
            
            ServerSocket.Close();   //关闭用于连接的套接字
            //socketAccept.Close();   //关闭与客户端绑定的套接字
            th1.Abort();    //关闭线程1

            CFlag = 0;  //将客户端标志重新设置为0,在进行连接时表示是打开的状态
            SFlag = 0;  //将连接成功标志程序设置为0,表示退出连接
            button_Accpet.Enabled = true;
            richTextBox_Receive.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
            richTextBox_Receive.Text += "服务器已关闭" + "\n";
            MessageBox.Show("服务器已关闭");
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击enter发送数据
        private void richTextBox_Send_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)//如果输入的是回车键  
            {
                this.button_Send_Click(sender, e);//触发button事件  
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击清除接收框
        private void button_Clear_Click(object sender, EventArgs e)
        {
            richTextBox_Receive.Clear();
        }
        #endregion
        /*****************************************************************/
    }
}

四、客户端完整代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Client
{
    public partial class Form1 : Form
    {
        public static Socket ClientSocket;  //声明负责通信的socket
        public static int SFlag = 0;    //连接服务器成功标志
        Thread th1;     //声明一个线程

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        { 
            Control.CheckForIllegalCrossThreadCalls = false;    //执行新线程时跨线程资源访问检查会提示报错,所以这里关闭检测
        }

        /*****************************************************************/
        #region 连接服务器端
        private void button_Connect_Click(object sender, EventArgs e)
        {
            ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //声明负责通信的套接字
            
            richTextBox_Recieve.Text += "正在连接...\n";
            
            IPAddress IP = IPAddress.Parse(textBox_Addr.Text);      //获取设置的IP地址
            int Port = int.Parse(textBox_Port.Text);       //获取设置的端口号
            IPEndPoint iPEndPoint = new IPEndPoint(IP,Port);    //指定的端口号和服务器的ip建立一个IPEndPoint对象

            try
            {  
                ClientSocket.Connect(iPEndPoint);       //用socket对象的Connect()方法以上面建立的IPEndPoint对象做为参数,向服务器发出连接请求
                SFlag = 1;  //若连接成功将标志设置为1
                    
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + textBox_Addr.Text + "连接成功" + "\n";
                button_Connect.Enabled = false;     //禁止操作连接按钮
                
                //开启一个线程接收数据
                th1 = new Thread(Receive);
                th1.IsBackground = true;
                th1.Start(ClientSocket);    
            }
            catch 
            {
                MessageBox.Show("服务器未打开");
            }    
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 接收服务器端数据
        void Receive(Object sk)
        {
            Socket socketRec = sk as Socket;

            while (true)
            {
                //5.接收数据
                byte[] receive = new byte[1024];
                ClientSocket.Receive(receive);  //调用Receive()接收字节数据

                //6.打印接收数据
                if (receive.Length > 0)
                {
                    richTextBox_Recieve.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "接收:";   //打印接收时间
                    //richTextBox_Recieve.Text += Encoding.ASCII.GetString(receive);  //将字节数据根据ASCII码转成字符串
                    richTextBox_Recieve.Text += Encoding.UTF8.GetString(receive);     //使用UTF8编码接收中文不会乱码
                    richTextBox_Recieve.Text += "\r\n";
                }
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 向服务器端发送数据
        private void button_Send_Click(object sender, EventArgs e)
        {
            //只有连接成功了才能发送数据,接收部分因为是连接成功才开启线程,所以不需要使用标志
            if (SFlag == 1)
            {
                byte[] send = new byte[1024];
                //send = Encoding.ASCII.GetBytes(richTextBox_Send.Text);  //将文本内容转换成字节发送
                send = Encoding.UTF8.GetBytes(richTextBox_Send.Text); //解决中文乱码问题
                ClientSocket.Send(send);    //调用Send()函数发送数据

                richTextBox_Recieve.Text += "*" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ") + "发送:";   //打印发送数据的时间
                richTextBox_Recieve.Text += richTextBox_Send.Text + "\n";   //打印发送的数据
                richTextBox_Send.Clear();   //清空发送框
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 关闭客户端
        private void buttonClose_Click(object sender, EventArgs e)
        {
            //保证是在连接状态下退出
            if (SFlag == 1)
            {
                byte[] send = new byte[1024];
                send = Encoding.ASCII.GetBytes("*close*");  //关闭客户端时给服务器发送一个退出标志
                ClientSocket.Send(send);
                
                th1.Abort();    //关闭线程
                ClientSocket.Close();   //关闭套接字
                button_Connect.Enabled = true;  //允许操作按钮
                SFlag = 0;  //客户端退出后将连接成功标志程序设置为0
                richTextBox_Recieve.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss  ");
                richTextBox_Recieve.Text += "客户端已关闭" + "\n";
                MessageBox.Show("已关闭连接");
            }
        }
        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击enter发送数据
        private void richTextBox_Send_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)//如果输入的是回车键  
            {
                this.button_Send_Click(sender, e);//触发button事件  
            }
        }

        #endregion
        /*****************************************************************/

        /*****************************************************************/
        #region 点击清除接收框
        private void button_Clear_Click(object sender, EventArgs e)
        {
            richTextBox_Recieve.Clear();
        }
        #endregion
        /*****************************************************************/
    }
}
  • 133
    点赞
  • 746
    收藏
    觉得还不错? 一键收藏
  • 42
    评论
学习以c#的必备书 本书介绍了如何使用Visual C++ 6.0编写Windows应用程序。全书内容全面、结构清晰、由浅入深、注重实用,并结合了大量的实例,以方便读者理解。本书既包含菜单、鼠标、键盘等基本操作,又包含图形编程、Internet程序设计、数据库编程等高级主题。每章讲述一个主题,在展示知识点的同时,提供具体的演示实例,最后两章详细讲解了两个综合实例,以使读者对Visual C++编程技术有整体的认识。 目 录 第一篇 Visual C++基础 第1章 Visual C++集成开发环境 2 1.1 Visual C++的版本 2 1.2 安装Visual C++ 6.0 3 1.3 Visual C++集成开发环境 6 1.3.1 菜单栏 7 1.3.2 工具栏 11 1.3.3 工作区窗口 13 1.3.4 输出窗口 14 1.4 编写第一个程序Hello World 14 1.4.1 创建Hello World工程 15 1.4.2 编译并执行程序 16 1.5 小结 16 第2章 使用Visual C++创建基本应用程序 17 2.1 理解Visual C++工程 17 2.2 Visual C++中应用程序的类型 18 2.2.1 控制台应用程序 18 2.2.2 基于对话框的应用程序 19 2.2.3 单文档应用程序 19 2.2.4 多文档应用程序 19 2.2.5 基于HTML文档的应用程序 19 2.3 创建应用程序框架 20 2.3.1 创建基于对话框的应用程序 20 2.3.2 创建单文档应用程序 21 2.3.3 创建多文档应用程序 23 2.3.4 创建基于HTML的应用程序 24 2.4 小结 25 第3章 面向对象程序设计基础 26 3.1 面向过程与面向对象 26 3.1.1 面向过程的问题 26 3.1.2 面向对象的特性 27 3.2 C++类的基本概念 27 3.2.1 结构体与类 27 3.2.2 类与对象 29 3.2.3 类的声明、定义和实现 29 3.2.4 成员变量 31 3.2.5 成员函数 32 3.2.6 静态成员 34 3.2.7 this指针 35 3.3 构造函数 37 3.3.1 使用构造函数的原因 37 3.3.2 构造函数的使用 37 3.3.3 重载构造函数 40 3.3.4 析构函数 41 3.4 继承 43 3.4.1 继承的概念 43 3.4.2 继承的工作机制 43 3.4.3 公有继承 44 3.4.4 私有继承 46 3.4.5 保护继承 47 3.4.6 多重继承 49 3.5 异常处理 51 3.5.1 异常的概念 51 3.5.2 异常处理机制 51 3.5.3 基本异常处理方法 52 3.5.4 多个异常处理方法 54 3.6 小结 56 第4章 Visual C++调试技术 57 4.1 Debug与Release 57 4.2 调试的过程 58 4.2.1 设置断点 58 4.2.2 控制程序的运行 60 4.3 使用查看工具 60 4.3.1 弹出式调试信息泡泡 61 4.3.2 变量窗口 61 4.3.3 观察窗口 61 4.3.4 快速查看窗口 62 4.3.5 内存查看窗口 62 4.3.6 寄存器窗口 62 4.3.7 调用堆栈窗口 63 4.4 其他调试技术 63 4.4.1 TRACE宏 63 4.4.2 ASSERT宏 64 4.4.3 VERIFY宏 64 4.5 小结 64 第二篇 Windows编程 第5章 Windows程序设计 66 5.1 Windows编程简介 66 5.1.1 Windows API概述 66 5.1.2 Windows应用程序中的常用术语 69 5.2 Windows应用程序运行机制 71 5.2.1 生成Windows应用程序框架 71 5.2.2 在Windows应用程序中添加代码 71 5.2.3 编译并执行程序 74 5.3 程序入口:WinMain()函数 74 5.3.1 WinMain()函数的定义 75 5.3.2 定义和注册窗口类 76 5.3.3 创建窗口 77 5.3.4 显示和更新窗口 78 5.3.5 消息循环 78 5.4 窗口过程函数和消息处理 80 5.4.1 窗口过程函数 80 5.4.2 常用消息的处理 82 5.5 小结 84 第6章 MFC编程概述 85 6.1 MFC概述 85 6.1.1 MFC设计原理 85 6.1.2 MFC特点 86 6.1.3 MFC AppWizard向导 86 6.2 MFC层次结构设计 88 6.2.1 单文档应用程序的构成 88 6.2.2 CObject类 90 6.2.3 CCmdTarget
第1章 进程、线程与网络协议  1.1 进程和线程  1.1.1 Process类  1.1.2 Thread类  1.1.3 在一个线程中操作另一个线程的控件 1.2 IP地址与端口  1.2.1 TCP/IP  1.2.2 IPAddress类与Dns类  1.2.3 IPHostEntry类  1.2.4 IPEndPoint类  1.3 套接字  1.3.1 Socket类  1.3.2 面向连接的套接字  1.3.3 无连接的套接字  1.4 网络流  习题  第2章 TCP应用编程  2.1 同步TCP应用编程  2.1.1 使用套接字发送和接收数据  2.1.2 使用NetworkStream对象发送和接收数据  2.1.3 TcpClient与TcpListener类  2.1.4 解决TCP的无消息边界问题  2.2 利用同步TCP编写网络游戏  2.2.1 服务器端编程  2.2.2 客户端编程  2.3 异步TCP应用编程  2.3.1 EventWaitHandle类  2.3.2 AsyncCallback委托  2.3.3 BeginAcceptTcpClient方法和EndAcceptTcpClient方法  2.3.4 BeginConnect方法和EndConnect方法  2.3.5 发送数据  2.3.6 接收数据  2.4 异步TCP聊天程序  2.4.1 服务器端设计  2.4.2 客户端设计  习题  第3章 UDP应用编程  3.1 UDP基础知识  3.2 UDP应用编程技术  3.2.1 UdpClient类  3.2.2 发送和接收数据的方法  3.3 利用UDP进行广播和组播  3.3.1 通过Internet实现群发功能  3.3.2 在Internet上举行网络会议讨论  习题  第4章 P2P应用编程  4.1 P2P基础知识  4.2 P2P应用举例  习题  第5章 SMTP与POP3应用编程  5.1 通过应用程序发送电子邮件  5.1.1 SMTP  5.1.2 发送邮件  5.2 利用同步TCP接收电子邮件  5.2.1 POP3工作原理  5.2.2 邮件接收处理  习题  第6章 网络数据加密与解密  6.1 对称加密  6.2 不对称加密  6.3 通过网络传递加密数据 6.4 Hash算法与数字签名  习题  第7章 三维设计与多媒体编程  7.1 简单的3D设计入门  7.2 DirectX基础知识  7.2.1 左手坐标系与右手坐标系  7.2.2 设备  7.2.3 顶点与顶点缓冲  7.2.4 Mesh对象  7.2.5 法线  7.2.6 纹理与纹理映射  7.2.7 世界矩阵、投影矩阵与视图矩阵  7.2.8 背面剔除  7.3 Primitive  7.4 Mesh  7.5 灯光与材质  7.6 音频与视频  7.7 直接使用SoundPlayer类播放WAV音频文件  习题

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值