客戶端要連接服務器:首先要知道服務器的IP地址。而服務器里有很多的應用程序,每一個應用程序對應一個端口號 所以客戶端想要與服務器中的某個應用程序進行通信就必須要知道那個應用程序的所在服務器的IP地址,及應用程序所對應的端口號
TCP協議:安全穩定,一般不會發生數據丟失,但是效率低。利用TCP發生數據一般經過3次握手(所有效率低,自己百度三次握手) UDP協議:快速,效率高,但是不穩定,容易發生數據丟失(沒有經過三次握手,不管服務器有空沒空,信息全往服務器發,所有效率搞,但服務器忙的時候就沒辦法處理你的數據,容易造成數據丟失,不穩定)
首先創建一個解決方案,在解決方案下創建一個“Socket通信”windows窗體應用程序的的項目,用作服務端,然后再在解決方案下創建一個“Socket客戶端”windows窗體應用程序的項目 用作客戶端
Socket通信 (服務器端)
- 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 Socket通信
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- this.txtPort.Text = "50000";
- this.txtIp.Text = "192.168.253.2";
- }
- private void btnStart_Click(object sender, EventArgs e)
- {
- try
- {
- //當點擊開始監聽的時候,在服務器端創建一個負責監聽IP地址跟端口號的Socket
- Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- //Any:提供一個 IP 地址,指示服務器應偵聽所有網絡接口上的客戶端活動。此字段為只讀。
- IPAddress ip = IPAddress.Any;
- //IPAddress ip = IPAddress.Parse(this.txtIp.Text);
- //創建端口號對象;將txtPort.Text控件的值設為服務端的端口號
- IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
- //監聽
- socketWatch.Bind(point);
- ShowMsg("監聽成功"); //注意:這個ShowMeg方法是自己定義的。看下面的代碼可以找到這個方法
- socketWatch.Listen(10);//連接隊列的最大長度 ;即:一個時間點內最大能讓幾個客戶端連接進來,超過長度就進行排隊
- //等待客戶端連接;Accept()這個方法能接收客戶端的連接,並為新連接創建一個負責通信的Socket
- Thread th = new Thread(Listen); //被線程執行的方法如果有參數的話,參數必須是object類型
- Control.CheckForIllegalCrossThreadCalls = false; //因為.net不允許跨線程訪問的,所以這里取消跨線程的檢查。.net不檢查是否有跨線程訪問了,所有就不會報: “從不是創建控件“txtLog”的線程訪問它” 這個錯誤了,從而實現了跨線程訪問
- th.IsBackground = true; //將th這個線程設為后台線程。
- //Start(object parameter); parameter:一個對象,包含線程執行的方法要使用的數據,即線程執行Listen方法,Listen的參數
- th.Start(socketWatch); //這個括號里的參數其實是Listen()方法的參數。因為Thread th = new Thread(Listen)這個括號里只能寫方法名(函數名) 但是Listen()方法是有參數的,所有就要用Start()方法將它的參數添加進來
- }
- catch
- { }
- }
- //將遠程連接的客戶端的IP地址和Socket存入集合中
- Dictionary<string, Socket> dic = new Dictionary<string, Socket>();
- /// <summary>
- /// 等待客戶端連接,如果監控到有客戶端連接進來就創建一個與之通信的Socket
- /// </summary>
- /// <param name="o"></param>
- Socket socketSend; // 定義一個負責通信的Socket
- void Listen(object o) //這里為什么不直接傳遞Socket類型的參數呢? 原因是:被線程執行的方法如果有參數的話,參數必須是object類型
- {
- Socket socketWatch = o as Socket;
- while (true) //為什么這里要有個while循環?因為當一個人連接進來的時候創建了與之通信的Socket后就程序就會往下執行了,就不會再回來為第二個人的連接創建負責通信的Socket了。(應該是每個人(每個客戶端)創建一個與之通信的Socket)所以要寫在循環里。
- {
- try
- {
- //等待客戶端連接;Accept()這個方法能接收客戶端的連接,並為新連接創建一個負責通信的Socket
- socketSend = socketWatch.Accept();
- dic.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //(根據客戶端的IP地址和端口號找負責通信的Socket,每個客戶端對應一個負責通信的Socket),ip地址及端口號作為鍵,將負責通信的Socket作為值填充到dic鍵值對中。
- //我們通過負責通信的這個socketSend對象的一個RemoteEndPoint屬性,能夠拿到遠程連過來的客戶端的Ip地址跟端口號
- ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "連接成功");//效果:192.168.1.32:連接成功
- comboBox1.Items.Add(socketSend.RemoteEndPoint.ToString()); //將連接過來的每個客戶端都添加到combBox控件中。
- //客戶端連接成功后,服務器應該接收客戶端發來的消息。
- Thread getdata = new Thread(GetData);
- getdata.IsBackground = true;
- getdata.Start(socketSend);
- }
- catch
- { }
- }
- }
- /// <summary>
- /// 服務端不停的接收客戶端發送過來的消息
- /// </summary>
- /// <param name="o"></param>
- void GetData(object o)
- {
- Socket socketSend = o as Socket;
- while (true)
- {
- try
- {
- //將客戶端發過來的數據先放到一個字節數組里面去
- byte[] buffer = new byte[1024 * 1024 * 2]; //創建一個字節數組,字節數組的長度為2M
- //實際接收到的有效字節數; (利用Receive方法接收客戶端傳過來的數據,然后把數據保存到buffer字節數組中,返回一個接收到的數據的長度)
- int r = socketSend.Receive(buffer);
- if (r == 0) //如果接收到的有效字節數為0 說明客戶端已經關閉了。這時候就跳出循環了。
- {
- //只有客戶端給用戶發消息,不可能是發0個長度的字節。即便發空消息,空消息也是有過個長度的。所有接收到的有效字節長度為0就代表客戶端已經關閉了
- break;
- }
- //將buffer這個字節數組里面的數據按照UTF8的編碼,解碼成我們能夠讀懂的的string類型,因為buffer這個數組的實際存儲數據的長度是r個 ,所以從索引為0的字節開始解碼,總共解碼r個字節長度。
- string str = Encoding.UTF8.GetString(buffer, 0, r);
- ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + str);
- }
- catch
- {
- }
- }
- }
- private void ShowMsg(string str)
- {
- txtLog.AppendText(str + "\r\n"); //將str這個字符串添加到txtLog這個文本框中。
- }
- /// <summary>
- /// 服務器給客戶端發送消息
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void btnSend_Click(object sender, EventArgs e)
- {
- if (comboBox1.SelectedItem == null) //如果comboBox控件沒有選中值。就提示用戶選擇客戶端
- {
- MessageBox.Show("請選擇客戶端");
- return;
- }
- //服務器要想給客戶端發消息,就需要先拿到負責通信的那個Socket
- string str = txtMes.Text.Trim();
- byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
- string getIp = comboBox1.SelectedItem as string; //comboBox存儲的是客戶端的(ip+端口號)
- socketSend = dic[getIp] as Socket; //根據這個(ip及端口號)去dic鍵值對中找對應 賦值與客戶端通信的Socket【每個客戶端都有一個負責與之通信的Socket】
- socketSend.Send(buffer);
- }
- }
- }
Socket客戶端 (客戶端)
- 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 Socket客戶端
- {
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- this.txtIp.Text = "192.168.253.2"; //因為我們要在客戶端連接服務端,所以這里是服務端的IP
- this.txtPort.Text = "50000"; //因為我們要在客戶端連接服務端,所以這里服務端的端口號
- }
- Socket socketSend;
- private void btnStart_Click(object sender, EventArgs e)
- {
- try
- {
- //創建負責通信的Socket
- socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- IPAddress ip = IPAddress.Parse(this.txtIp.Text);
- //創建一個IPEndPoint類的實例,用遠程服務器的IP地址和端口號來初始化它
- IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(this.txtPort.Text));
- //獲得要連接的遠程服務器應用程序的IP地址和端口號,並建立與遠程服務器的連接
- socketSend.Connect(point);
- ShowMsg("連接成功");
- //開啟一個新的線程,不停的接收服務端發送過來的消息
- Thread th = new Thread(GetData);
- th.IsBackground = true; //IsBackground獲取或設置一個值,該值指示某個線程是否為后台線程。
- th.Start();
- }
- catch
- {
- }
- }
- void ShowMsg(string str)
- {
- this.txtLog.AppendText("\r\n"+str + "\r\n");
- }
- /// <summary>
- /// 客戶端不停的接收服務端發送過來的消息
- /// </summary>
- /// <param name="o"></param>
- void GetData(object o)
- {
- while (true)
- {
- try
- {
- //將服務端發過來的數據先放到一個字節數組里面去
- byte[] buffer = new byte[1024 * 1024 * 2]; //創建一個字節數組,字節數組的長度為2M
- //實際接收到的有效字節數; (利用Receive方法接收客戶端傳過來的數據,然后把數據保存到buffer字節數組中,返回一個接收到的數據的長度)
- int r = socketSend.Receive(buffer);
- if (r == 0) //如果接收到的有效字節數為0 說明客戶端已經關閉了。這時候就跳出循環了。
- {
- //只有客戶端給用戶發消息,不可能是發0個長度的字節。即便發空消息,空消息也是有過個長度的。所有接收到的有效字節長度為0就代表客戶端已經關閉了
- break;
- }
- //將buffer這個字節數組里面的數據按照UTF8的編碼,解碼成我們能夠讀懂的的string類型,因為buffer這個數組的實際存儲數據的長度是r個 ,所以從索引為0的字節開始解碼,總共解碼r個字節長度。
- string str = Encoding.UTF8.GetString(buffer, 0, r);
- //RemoteEndPoint方法是獲取服務器的IP地址和端口號
- ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + str);
- }
- catch
- {
- }
- }
- }
- /// <summary>
- /// 客戶端給服務器發送消息
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void btnSend_Click(object sender, EventArgs e)
- {
- //獲取輸入框輸入的數據
- string str = txtMes.Text.Trim();
- //將輸入框的數據轉換成二進制數據
- byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str);
- //Send方法是將數據發送到連接的Socket
- socketSend.Send(buffer);
- this.txtLog.AppendText("我自己:" + this.txtMes.Text);
- this.txtMes.Text = "";
- }
- private void Form1_Load(object sender, EventArgs e)
- {
- //在程序加載的時候取消跨線程的檢查
- Control.CheckForIllegalCrossThreadCalls = false;
- }
- }
- }
當這兩個項目(服務端,和客戶端)都寫好后,怎么測試呢?
首先我們將服務端設置啟動項,然后啟動調試,
然后我們在將鼠標移動到“Socket客戶端” (客戶端)這個項目下,鼠標右鍵項目名稱“Socket客戶端”--》調試--》啟動實例 就可以了。
開打開始命令 cmd telnet 10.18.16.46 5000 即telnet 服務器ip地址 綁定的端口號
如果用命令,需要在 控制面板--》程序和功能--》打開或關閉windows功能 將Telnet 服務器,和Telnet客戶端打上鈎
---------------------------------------------------------------------------------------------------------------
注釋一下:
//創建一個用來監聽的Socket對象(參數1:表示采用IPv4,參數2:表示使用數據流來傳輸數據,而不是數據包 參數3:表示采用Tcp協議)
Socket skConn =newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
//創建IP和監聽端口(參數1:IP地址 參數2:端口號是9999)
IPEndPoint endPoint=newIPEndPoint(IPAddress.Parse("192.168.253.3"),9999);