參考:http://www.cnblogs.com/jzxx/p/5630516.html
一、原作者的這段話很好,先引用一下:
Socket的Send方法,並非大家想象中的從一個端口發送消息到另一個端口,它僅僅是拷貝數據到基礎系統的發送緩沖區,然后由基礎系統將發送緩沖區的數據到連接的另一端口。值得一說的是,這里的拷貝數據與異步發送消息的拷貝是不一樣的,同步發送的拷貝,是直接拷貝數據到基礎系統緩沖區,拷貝完成后返回,在拷貝的過程中,執行線程會IO等待, 此種拷貝與Socket自帶的Buffer空間無關,但異步發送消息的拷貝,是將Socket自帶的Buffer空間內的所有數據,拷貝到基礎系統發送緩沖區,並立即返回,執行線程無需IO等待,所以異步發送在發送前必須執行SetBuffer方法,拷貝完成后,會觸發你自定義回調函數ProcessSend,在ProcessSend方法中,調用SetBuffer方法,重新初始化Buffer空間。
二、代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using System.IO; using System.Threading; namespace TcpClientTest { public partial class FormMain : Form { public FormMain() { InitializeComponent(); } private void FormMain_Load(object sender, EventArgs e) { //初始化控件 txtSendMssg.Text = "測試數據"; //打開Listener開始監聽 Thread thrListener = new Thread(new ThreadStart(Listen)); thrListener.Start(); } private void FormMain_FormClosing(object sender, FormClosingEventArgs e) { //強制關閉程序(強行終止Listener) Environment.Exit(0); } //發送數據 private void btnSend_Click(object sender, EventArgs e) { TcpClient tcpClient = new TcpClient(); //tcpClient.Connect(IPAddress.Parse("170.0.0.78"), 2014); tcpClient.Connect(IPAddress.Parse("127.0.0.1"), 2014); NetworkStream ntwStream = tcpClient.GetStream(); if (ntwStream.CanWrite) { Byte[] bytSend = Encoding.UTF8.GetBytes(txtSendMssg.Text); ntwStream.Write(bytSend, 0, bytSend.Length); } else { MessageBox.Show("無法寫入數據流"); ntwStream.Close(); tcpClient.Close(); return; } ntwStream.Close(); tcpClient.Close(); } //監聽數據 private void Listen() { Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Any, 2014)); //不斷監聽端口 while (true) { listener.Listen(0); Socket socket = listener.Accept(); NetworkStream ntwStream = new NetworkStream(socket); StreamReader strmReader = new StreamReader(ntwStream); Invoke(new PrintRecvMssgDelegate(PrintRecvMssg), new object[] { strmReader.ReadToEnd() }); socket.Close(); } //程序的listener一直不關閉 //listener.Close(); } //線程內向文本框txtRecvMssg中添加字符串及委托 private delegate void PrintRecvMssgDelegate(string s); private void PrintRecvMssg(string info) { txtRecvMssg.Text += string.Format("[{0}]:{1}\r\n", DateTime.Now.ToLongTimeString(), info); } } }
需要在項目上加兩個TextBox(名字分別為 txtSendMssg、txtRecvMssg)和一個Button(名字為 btnSend)
三、運行效果(效果圖片見原作者文章)
在發送數據的文本框中分別輸入“千山鳥飛絕”、“萬徑人蹤滅”、“孤舟蓑笠翁”、“獨釣寒江雪”四句話,輸完一句話,單擊一次“發送數據”按鈕,就可以在接收數據里看到這四句話了。上面代碼中,信息的發送時通過TcpClient連接到127.0.0.1的2014端口,信息的接收是通過Listen函數不斷監聽本機的2014端口實現的。從自己創建的線程中修改控件信息,用到了委托。
四、補充
1、運行時提示:由於目標計算機積極拒絕,無法連接。 127.0.0.1:2014
用 System.Net.Dns.GetHostAddresses("localhost")[1].ToString(); 取得的也是127.0.0.1
參考:https://blog.csdn.net/u010784236/article/details/51820284 也與我的情況不一樣。
想想我是在局域網中,用ipconfig /all 找到自己的ip 192.168.3.5 替換 127.0.0.1 添加防火牆規則,仍不行。關閉防火牆,還不行。
(如何獲得IP,還可參考:https://blog.csdn.net/fwj380891124/article/details/18214145)
正准備放棄時,看到 https://blog.csdn.net/fengzheng22/article/details/17266105 其中有一句:
需要你用tcpclient訪問的IP的端口正在被監聽,否則就會顯示積極拒絕,不是看他是否被占用,要看他是否在監聽
想想我是直接整體復制的代碼,不是雙擊窗體后單獨寫的formMain_load代碼。而服務端應該是在formMain_load時開始監聽。
於是重新修改代碼,使得formMain_load時先運行服務端監聽的代碼。重新生成並運行,正常。這個錯誤太低級。
如果在局域網兩台電腦上分別運行客戶端和服務端,要確保能ping通,檢查防火牆規則。參考這里:https://jingyan.baidu.com/article/a65957f4f557cb24e67f9ba6.html
2、另外,這里還有個例子:https://www.jb51.net/article/130148.htm
3、參考:https://www.cnblogs.com/straight/articles/7660889.html
socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。我的理解就是Socket就是該模式的一個實現,socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)。
如果作為一個服務器,在調用socket()、bind()之后就會調用listen()來監聽這個socket,如果客戶端這時調用connect()發出連接請求,服務器端就會接收到這個請求。
isten函數的第一個參數即為要監聽的socket描述字,第二個參數為相應socket可以排隊的最大連接個數。socket()函數創建的socket默認是一個主動類型的,listen函數將socket變為被動類型的,等待客戶的連接請求。
TCP服務器端依次調用socket()、bind()、listen()之后,就會監聽指定的socket地址了。TCP客戶端依次調用socket()、connect()之后就向TCP服務器發送了一個連接請求。TCP服務器監聽到這個請求之后,就會調用accept()函數取接收請求,這樣連接就建立好了。之后就可以開始網絡I/O操作了,即類同於普通文件的讀寫I/O操作。
4、TCP協議三次握手過程分析 參考:http://www.cnblogs.com/rootq/articles/1377355.html
5、C# TCP多線程服務器示例 參考:https://www.cnblogs.com/zhangxiaoyong/p/6486311.html
6. C# socket端口復用-多主機頭綁定 參考:https://www.cnblogs.com/viewcozy/p/4666137.html
7、定時執行、一對多 參考: http://www.cnblogs.com/chenxizhang/archive/2011/09/10/2172994.html
8、在同步模式中,在服務器上使用Accept方法接入連接請求,而在客戶端則使用Connect方法來連接服務器。相對地,在異步模式下,服務器可以使用BeginAccept方法和EndAccept方法來完成連接到客戶端的任務,在客戶端則通過BeginConnect方法和EndConnect方法來實現與服務器的連接。 參考:http://www.cnblogs.com/sunev/archive/2012/08/07/2625688.html
9、C#的IPAddress IPEndPoint 參考 https://www.cnblogs.com/2Yous/p/5797592.html