Socket
網路程式設計對于B/S項目來說,幾乎不會涉及;但是如果涉及遊戲伺服器開發,或者上位機伺服器開發,自定義通信協定,
Socket
網絡程式設計就變得常見了。
Socket程式設計
1.C#的socket
- 1.建立
對象,指定傳輸層協定Socket
或者TCP
-UDP
Socket
//建立一個負責監聽IP位址跟端口号的Socket
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
- 2.綁定端口 -
Bind()
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 9999));
- 3.監聽
serverSocket.Listen();
- 4.阻塞,等待用戶端連接配接 -
Accept
Socket client = serverSocket.Accept();
- 5.用戶端連接配接 -
與發送資訊 -Connect()
Send()
clientSocket.Connect(ip, port);
- 6.服務端解除阻塞,接收消息 -
Receive()
byte[] msg = new byte[1024 * 1024 * 2];
int msgLen = client.Receive(msg);
後面便是周而複始的,接收、發送的戲份。
在整個過程中,有以下步驟需要多線程處理:
-
:由于服務端Accept()
操作會阻塞線程,是以需要多線程,使其每接收一個用戶端連接配接,就開一個線程進行獨立處理。Accept()
-
:由于Receive()
操作也會阻塞線程,是以也需要開啟線程,才能進行與用戶端或伺服器的互動操作。Receive()
1.1 服務端
class Program
{
static void Main(string[] args)
{
Server server = new Server();
server.Start();
Console.ReadKey();
}
}
public class Server
{
private Socket serverSocket;
//泛型集合 或者 字典
private List<Socket> clientList;
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
public Server()
{
//建立一個負責監聽IP位址跟端口号的Socket
serverSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
clientList = new List<Socket>();
}
public void Start()
{
//監聽 telnet 192.168.11.78 9999
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 9999));
serverSocket.Listen(10);
Console.WriteLine("Server Start...");
Thread threadAccept = new Thread(Accept);
threadAccept.IsBackground = true;
threadAccept.Start();
}
/// <summary>
/// serverSocket可以作為參數 object
/// </summary>
private void Accept()
{
//等待用戶端的連接配接,會挂起目前線程(如果是winfrom wpf 主線程裡使用這個方法會卡死) 接收用戶端請求,并為之建立通信的socket---負責通信
Socket client = serverSocket.Accept();//等待連接配接 是以要開啟線程
//拿到遠端用戶端的IP位址和端口号
IPEndPoint clientDetail = client.RemoteEndPoint as IPEndPoint;
Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} connecting");
//存儲用戶端List
clientList.Add(client);
dicSocket.Add(clientDetail.ToString(), client);
Thread receiveAccept = new Thread(Receive);
receiveAccept.IsBackground = true;
receiveAccept.Start(client);
//按順序執行,尾遞歸便于了解 假死,一個用戶端
Accept();//如果有一個連接配接了 就會依次執行 接收好用戶端後的處理,是以要加上一個尾遞歸
////或者使用循環
//while (true)
//{
// //上面所有的代碼,排除尾遞歸
//}
}
public void Receive(object obj)
{
Socket client = obj as Socket;
IPEndPoint clientDetail = client.RemoteEndPoint as IPEndPoint;
try
{
byte[] msg = new byte[1024 * 1024 * 2];
//實際接收到到的有效位元組數 遠端用戶端一關 就接收不到 msgLen=0
int msgLen = client.Receive(msg);
Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} say:{Encoding.UTF8.GetString(msg, 0, msgLen)}");
if (msgLen != 0)
{
//client.Send(Encoding.UTF8.GetBytes("樓上說的對"));
//改造後
Broadcast(client, $"伺服器時間{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},ip{clientDetail.Address} {clientDetail.Port} say:{Encoding.UTF8.GetString(msg, 0, msgLen)}");
//尾遞歸 不停的接收用戶端消息 同上可使用循環
Receive(client);
}
}
catch
{
Console.WriteLine($"ip{clientDetail.Address} {clientDetail.Port} 斷開");
clientList.Remove(client);
}
}
private void Broadcast(Socket socketOther, string msg)
{
//周遊用戶端
foreach (var client in clientList)
{
if (client != socketOther)
{
client.Send(Encoding.UTF8.GetBytes(msg));
}
}
}
}
1.2 用戶端
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Connect("127.0.0.1", 9999);
Console.WriteLine("請輸入聊天内容,輸入quit退出:");
string msg = Console.ReadLine();
while (msg != "quit")
{
client.Send(msg);
msg = Console.ReadLine();
}
Console.ReadKey();
}
}
public class Client
{
private Socket clientSocket;
public Client()
{
//建立負責通信的socket
this.clientSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
}
public void Connect(string ip, int port)
{
clientSocket.Connect(ip, port);
Console.WriteLine("Client connect success...");
Thread receiveAccept = new Thread(Receive);
receiveAccept.IsBackground = true;
receiveAccept.Start();
//Receive();
}
private void Receive()
{
try
{
byte[] msg = new byte[1024*1024*2];
int msgLen = clientSocket.Receive(msg);
Console.WriteLine($"Server say:{Encoding.UTF8.GetString(msg, 0, msgLen)} ");
}
catch (Exception)
{
Console.WriteLine("伺服器斷開");
}
Receive();
}
public void Send(string msg)
{
clientSocket.Send(Encoding.UTF8.GetBytes(msg));
}
}
2.Golang的socket
2.1 服務端
Golang
建立服務端省略了些步驟,直接從監聽
Listen
開始,部落客開始把
goroutine
作線程類比C#的寫法,也是沒問題的。後面參考了包中的示例
ln, err := net.Listen("tcp", ":8080")
if err != nil {
// handle error
}
for {
conn, err := ln.Accept()
if err != nil {
// handle error
}
go handleConnection(conn)
}
利用死循環、
goroutine
與
Accept()
傳回多個值。可以有效簡化代碼。
package main
import (
"bufio"
// "encoding/binary"
// "encoding/json"
"fmt"
"net"
)
func handleConnection(conn net.Conn) {
defer conn.Close() // 關閉連接配接
for {
reader := bufio.NewReader(conn)
var buf [128]byte
n, err := reader.Read(buf[:]) // 讀取資料
if err != nil {
fmt.Println("read from client failed, err:", err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client端發來的資料:", recvStr)
conn.Write([]byte(recvStr)) // 發送資料
}
}
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:9999")
if err != nil {
// handle error
fmt.Println("listen failed, err:", err)
return
}
for {
conn, err := listen.Accept() // 建立連接配接
if err != nil {
// handle error
fmt.Println("accept failed, err:", err)
continue
}
go handleConnection(conn)
}
}
22 用戶端
用戶端方面有一點不同,
net
包裡有單獨方法
Dial()
,大概翻譯了一下叫: 撥号
package main
import (
"bufio"
// "encoding/binary"
// "encoding/json"
"fmt"
"net"
"os"
"strings"
)
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:9999")
if err != nil {
fmt.Println("err :", err)
return
}
defer conn.Close() // 關閉連接配接
inputReader := bufio.NewReader(os.Stdin)
fmt.Println("請輸入内容,按回車發送,按q鍵退出...")
for {
//讀取輸入流,直到換行符出現為止
input, _ := inputReader.ReadString('\n') // 讀取使用者輸入
//截取回車與換行
inputInfo := strings.Trim(input, "\r\n")
if strings.ToUpper(inputInfo) == "Q" { // 如果輸入q就退出
return
}
_, err = conn.Write([]byte(inputInfo)) // 發送資料
if err != nil {
fmt.Println(err)
return
}
buf := [512]byte{}
n, err := conn.Read(buf[:])
if err != nil {
fmt.Println("recv failed, err:", err)
return
}
fmt.Println(string(buf[:n]))
}
}
再次強調:這個系列并不是教程,如果想系統的學習,部落客可推薦學習資源。
作者:Garfield
同步更新至個人部落格:http://www.randyfield.cn/
本文版權歸作者所有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected]
微信公衆号
掃描下方二維碼關注個人微信公衆号,實時擷取更多幹貨

同步更新至:http://www.randyfield.cn/
出處:http://www.cnblogs.com/RandyField/
本文版權歸作者和部落格園共有,未經許可禁止轉載,否則保留追究法律責任的權利,若有需要請聯系[email protected].