C#Socket編程
一、簡單了解服務端和客戶端各自的功能。
首先應該清楚服務端(Server)和客戶端(Client)它們各自的功能。
(1)服務端(Server):
負責接收客戶端的請求,然后根據客戶端請求的內容不同而給客戶端返回相應的數據。
(2)客戶端(Client):
鏈接服務端,向服務端發送自己的業務需求(也就是數據),然后接受服務端返回過來的信息。
(3)分析服務端和客戶端的功能,可以很清楚的知道,它們完成了數據之間的交流,或者說是業務之間的相互傳遞與獲取。
二、服務器與客戶端之間信息傳遞的橋梁(Socket)
(1)服務器和客戶端進行信息傳遞的通道,socket套接字分為很多種類型,它是一個協議族,常用的協議TCP/IP和UDP兩種。
(2)簡單來說就是通過socket協議能夠進行通信,每種編程語言socket的寫法都八九不離十,創建socket通信的步驟都十分接近。
(3)服務端socket和客戶端socket通過對方的IP地址和對應應用程序的PORT(端口號)進行連接和數據傳輸。
三、C#中創建socket的一般方式以及大致業務流程
(1)首先定義一個Socket套接字對象(這里協議族的類型我們選擇TCP協議,TCP傳輸數據時安全、穩定、可靠,當然對應的性能比UDP差)
Socket socket_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
(2)不管是客戶端還是服務端,創建socket套接字的方式都一樣,但是服務端(Server需要綁定該服務端的IP和端口號,以便讓客戶端進行連接),為了方便這里我綁定的是本地的IP(LocalHost)
IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
int port = 8888;
(3)為服務端綁定IP和端口
socket_server.Bind(new IPEndPoint(iPAddress, port));
(4) 綁定好IP和PORT后就開始監聽(將創建的套接字變為監聽套接字,以及設置最大同時監聽數量)
socket_server.Listen(100);
(5)監聽完畢就可以等待客戶端的連接了(注意:如果沒有客戶端連接到來,那么Accept()方法會一直處於阻塞狀態,會 阻塞當前線程,直到有連接到來,線程才會接阻塞,然后將客戶端的套接字儲存下來,以便接下來的數據傳輸使用。)
Socket clientSocket = socket_server.Accept();
(6)接受客戶端的消息(使用我們上一步儲存的客戶端套接字來接收)
//這里接受消息的方式是以字節流來接收的,所有用一個byte類型的數組來儲存,Receive方法還返回接受直接的實際長度,Encoding.UTF8.GetString()方法將byte類型的數據轉化為字符串
byte[] strbyte = new byte[1024]; int count = clientsocket.Receive(strbyte); string ret = Encoding.UTF8.GetString(strbyte,0, count)
(7)向客戶端發送消息(發送消息使用Send方法進行)
Console.WriteLine("請輸入要發送的消息:"); string str = Console.ReadLine(); byte[] strbyte = Encoding.UTF8.GetBytes(str); clientsocket.Send(strbyte);
四、完整的服務端代碼如下:
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace SocketServer { /// <summary> /// 服務端 /// </summary> class Serversocket { /// <summary> /// 服務端入口 /// </summary> private static Serversocket socketserver; public static Serversocket Instance() { if (socketserver == null) { socketserver = new Serversocket(); } return socketserver; } private Socket server; private IPAddress iPAddress; private int port; /// <summary> /// 初始化數據 /// </summary> public void Init() { iPAddress = IPAddress.Parse("127.0.0.1"); port = 8888; CreateSocket(); BindAndListen(); WaitClientConnection(); } /// <summary> /// 1.創建服務端的套接字 /// </summary> private void CreateSocket() { server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } /// <summary> /// 2.綁定服務器ip和端口 /// </summary> private void BindAndListen() { server.Bind(new IPEndPoint(iPAddress, port)); server.Listen(100); } /// <summary> /// 用一個列表儲存連接成功的客戶端 /// </summary> private List<Socket> ConnectSockeList = new List<Socket>(); /// <summary> /// 用一個字典儲存所有接受客戶端消息以及發送給客戶端消息的線程 /// </summary> private Dictionary<int, List<Thread>> recvThreadList = new Dictionary<int, List<Thread>>(); /// <summary> /// 3.等待客戶端的連接 /// </summary> private void WaitClientConnection() { int index = 1; while (true) { Console.WriteLine("當前鏈接數量:"+recvThreadList.Count); Console.WriteLine("等待客戶端的鏈接:"); Socket ClientSocket = server.Accept(); if (ClientSocket!=null) { Console.WriteLine("{0}連接成功!",ClientSocket.RemoteEndPoint); ConnectSockeList.Add(ClientSocket); //創建接受客戶端消息的線程,並將其啟動 Thread recv = new Thread(RecvMessage); recv.Start(new ArrayList { index, ClientSocket }); Thread send = new Thread(SendMessage); send.Start(new ArrayList { index, ClientSocket }); recvThreadList.Add(index, new List<Thread> { recv, send }); index++; } } } /// <summary> /// 接受客戶端的消息 /// </summary> /// <param name="clientsocket"></param> private void RecvMessage(object client_socket) { ArrayList arraylist = client_socket as ArrayList; int index = (int)arraylist[0]; Socket clientsocket = arraylist[1] as Socket; while (true) { try { byte[] strbyte = new byte[1024]; int count = clientsocket.Receive(strbyte); string ret = Encoding.UTF8.GetString(strbyte,0, count); Console.WriteLine("{0}給你發送了消息:{1}",clientsocket.RemoteEndPoint,ret); } catch (Exception) { //客戶端離去時終止線程 Console.WriteLine("代號為:{0}的客戶端已經離去!",index); recvThreadList[index][0].Abort(); } } } /// <summary> /// 向客戶端返回消息 /// </summary> private void SendMessage(object client_socket) { ArrayList arraylist = client_socket as ArrayList; int index = (int)arraylist[0]; Socket clientsocket = arraylist[1] as Socket; while (true) { try { Console.WriteLine("請輸入要發送的消息:"); string str = Console.ReadLine(); byte[] strbyte = Encoding.UTF8.GetBytes(str); clientsocket.Send(strbyte); } catch (Exception) { Console.WriteLine("代號為:{0}的客戶端已經離去!消息發送失敗!"); recvThreadList[index][1].Abort(); recvThreadList.Remove(index); } } } } }
啟動服務器的代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SocketServer { class Program { static void Main(string[] args) { Serversocket.Instance().Init(); Console.ReadKey(); } } }
啟動后的運行效果:
五、上面的服務端代碼允許多個客戶端同時訪問,而且能對不同的客戶端分開處理主要應用到了C#中的Thead模塊
(1)創建一條線程
Thread t1 = new Thread(對應要執行的函數)
//Thread方法有四個重載,常用的三個
public Thread(ThreadStart start);//接受一個無參數的且無返回值的委托
public Thread(ParameterizedThreadStart start);//接受一個帶object類型參數的且無返回值的委托
public Thread(ThreadStart start, int maxStackSize);//接受一個無參數的且無返回值的委托,可以指定線程最大堆棧大小
(2)啟動一條線程
t1.start()//可以指定object類型的參數
(3)終止一條線程
t1.Abort()
(4)如果不用多線程去處理單個客戶端的消息發送和接受,那么當有多個客戶端連接時,如果其中一個客戶端發生阻塞,那么后面的所有客戶端都將阻塞。
六、簡單客戶端代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace Socket_Client_02 { /// <summary> /// 客戶端 /// </summary> class Socket_Client { public Socket_Client(string ip,int port) { this.iPAddress = IPAddress.Parse(ip); this.port = port; } /// <summary> /// 套接字 /// </summary> private Socket client_socket; /// <summary> /// 客戶端要連接的ip地址 /// </summary> private IPAddress iPAddress; /// <summary> /// 客戶端要連接的端口好 /// </summary> private int port; /// <summary> /// 創建客戶端連接的套接字 /// </summary> /// <returns></returns> public Socket Create_Client_Socket() { return new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } /// <summary> /// 連接服務器 /// </summary> public void Connect_Server() { client_socket = Create_Client_Socket(); //tcp連接服務器的時候只需要連接一次,因為tcp是長鏈接 client_socket.Connect(new IPEndPoint(iPAddress, port)); } /// <summary> /// 接收來自服務器的消息 /// </summary> public void Recv_Msg_By_Client() { while (true) { byte[] ser_msg = new byte[1024]; int count = client_socket.Receive(ser_msg); string str_msg = Encoding.UTF8.GetString(ser_msg, 0, count); if (count > 0) { Console.WriteLine("接收到來自{0}的消息為:{1}", client_socket.RemoteEndPoint, str_msg); } } } /// <summary> /// 向服務器發送請求 /// </summary> public void Request_Client() { while (true) { Console.WriteLine("請輸入你要發送到服務器的消息:"); string send_msg = Console.ReadLine(); byte[] by_msg = Encoding.UTF8.GetBytes(send_msg); client_socket.Send(by_msg); } } } }
客戶端啟動代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Socket_Client_02 { class Program { static void Main(string[] args) { Console.WriteLine("請輸入你要連接的服務器的ip地址:"); string ip = Console.ReadLine(); Console.WriteLine("請輸入你要連接的服務器的端口號:"); int port = int.Parse(Console.ReadLine()); //創建套接字 Socket_Client s = new Socket_Client(ip, port); //連接服務器 s.Connect_Server(); //接收服務器的消息 Thread recv = new Thread(s.Recv_Msg_By_Client); //給服務器發送消息 Thread send = new Thread(s.Request_Client); recv.Start(); send.Start(); recv.Join(); send.Join(); Console.ReadKey(); } } }