最近在做個多人遊戲的demo,試試自己編寫服務器端,在寫之前,就不介紹Tcp和Udp的區別了,網上資料很多,在看socket編程之前最好先去看看基礎的Tcp和Udp的理論內容,再去看Socket編程會好很多。
C#的socket編程主要用到一下命名空間:
- System.Net;//socket的重要基礎
- System.Net.Sockets;//socket的重要基礎
- System.Text;//因爲socket只能發送byte數組,也只能接收byte數組,需要把接收到的byte數組轉化爲字符串
- System.Threading;//主要用來開啓一個負責監聽和接收消息的線程
socket的常用類和方法
相關類:
- IPAddress:包含了一個IP地址
- IPEndPoint:包含了一對IP地址和端口號
相關方法
- Socket():創建一個Socket
- Bind():綁定一個本地的IP和端口號(IPEndPoint)
- Listen():讓Socket偵聽傳入的連接吃那個病,並指定偵聽隊列容量
- Connect():初始化與另一個Socket的連接
- Accept():接收連接並返回一個新的Socket
- Send():輸出數據到Socket,只能發送一個byte數組
- Receive():從Socket中讀取數據,返回接收到的數據的長度
- Close():關閉Socket,銷燬連接
客戶端和服務器端結構如圖
通過上圖,我們可以看出,首先服務器會創建一個負責監聽的socket,然後客戶端通過socket連接到服務器指定端口,最後服務器端負責監聽的socket,監聽到客戶端有連接過來了,就創建一個負責和客戶端通信的socket。
下面我們來看下Socket更具體的通信過程:
Socket的通訊過程
服務器端:
01,申請一個socket
02,綁定到一個IP地址和一個端口上
03,開啓偵聽,等待接收連接
客戶端:
01,申請一個socket
02,連接服務器(指明IP地址和端口號)
服務器端接收到連接請求後,產生一個新的socket(端口大於1024)與客戶端建立連接並進行通信,原監聽socket繼續監聽。
注意:負責通信的Socket不能無限創建,創建的數量和操作系統有關。
Socket的構造函數
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用來解析地址的尋址方案。例如:InterNetWork指示當Socket使用一個IP版本4地址連接
SocketType:定義要打開的Socket的類型
Socket類使用ProtocolType枚舉向Windows Sockets API通知所請求的協議
示例
IPAddress addr = IPAddress.Parse("127.0.0.1");
/*
注意如果addr是指定ip地址,那麼就只能在同一個ip下進行通信,如果作爲服務器端的話addr必須是IpAddress.any,
表示任意ip都可以在指定端口下訪問服務器
*/
IPEndPoint endp = new IPEndPoint(addr,,9000);
服務端先綁定:serverWelcomeSocket.Bind(endp)
客戶端再連接:clientSocket.Connect(endp)
注意
1,一個Socket一次只能連接一臺主機
2,Socket關閉後無法再次使用
3,每個Socket對象只能與一臺遠程主機連接。如果你想連接到多臺遠程主機,你必須創建多個Socket對象。
服務器端代碼
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server {
class Program {
//記錄通信用的scoket的字典
static Dictionary<string, Socket> sockets = new Dictionary<string, Socket> ();
static void Main (string[] args) {
//設置ip和端口
IPAddress ipAddress = IPAddress.Any;
int port = 30001;
//綁定本機的IP和端口
IPEndPoint endPoint = new IPEndPoint (ipAddress, port);
//新建socket
Socket socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//綁定服務器的監聽Socket
try {
socket.Bind (endPoint);
//設置最大連接數
socket.Listen (10); //如果是0表示不限制連接數
ShowMessage ("開始監聽");
// 後臺線程用於監聽來連接上來的客戶端
Thread t = new Thread (AcceptInfo);
t.IsBackground = true;
t.Start (socket);
} catch (Exception e) {
// 打印錯誤信息
ShowMessage (e.Message);
}
Console.ReadKey ();
}
//打印信息
static void ShowMessage (string msg) {
Console.WriteLine (msg);
}
//創建鏈接成功後用於通信的Socket
static void AcceptInfo (Object o) {
Socket socket = o as Socket;
while (true) {
try {
//通信用的socket,根據連接過來的scoket返回一個新的用於通信的socket
Socket tSocket = socket.Accept ();
string point = tSocket.RemoteEndPoint.ToString ();
//把通信socket加入字典
sockets.Add (point, tSocket);
ShowMessage (point + " 連接成功");
//接收客戶端的信息
Thread t = new Thread (ReceiveMsg);
t.IsBackground = true;
t.Start (tSocket);
} catch (Exception e) {
ShowMessage (e.Message);
break;
}
}
}
static void ReceiveMsg (Object o) {
Socket client = o as Socket;
while (true) {
try {
byte[] buffer = new byte[1024 * 1024];
// 獲得實際接受的數據的長度
int n = client.Receive (buffer);
// 如果是一個有內容的請求
if (n > 0) {
GetRequestByCode (buffer[0], buffer, n);
}
// string words = Encoding.UTF8.GetString(buffer,0,n);
// ShowMessage(client.RemoteEndPoint.ToString() + ":" + words);
} catch (Exception e) {
ShowMessage (e.Message);
break;
}
}
}
/// <summary>
/// 根據請求的code來獲得對應的請求,即buffer[0] = code
/// </summary>
/// <param name="code">請求的code</param>
/// <param name="buffer">請求的內容</param>
/// <param name="length">實際請求內容的長度</param>
static void GetRequestByCode (byte code, byte[] buffer, int length) {
switch (code) {
case 0:
GetRequestContent (buffer, length);
break;
case 1:
GetRequestContent (buffer, length);
break;
default:
break;
}
}
/// <summary>
/// 負責獲取請求的內容
/// </summary>
/// <param name="buffer"></param>
/// <param name="length"></param>
static void GetRequestContent (byte[] buffer, int length) {
//因爲指定了buffer[0]是請求的code區分是哪個請求,請求內容從1開始
string words = Encoding.UTF8.GetString (buffer, 1, length);
Console.WriteLine (words);
}
}
}
客戶端代碼
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Client {
class Program {
static Socket client = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
static void Main (string[] args) {
IPAddress ip = IPAddress.Parse ("39.107.234.74");
IPEndPoint point = new IPEndPoint (ip, 30001);
//開始連接到服務器
try {
client.Connect (point);
ShowMessage ("服務器" + client.RemoteEndPoint.ToString ());
ShowMessage ("客戶端" + client.LocalEndPoint.ToString ());
SendMessage (client, 0, "我是" + client.LocalEndPoint.ToString () + "請求0過來了");
// SendMessage(client,1,"我是" + client.LocalEndPoint.ToString() + "請求1過來了");
} catch (Exception e) {
ShowMessage (e.Message);
}
Console.ReadKey ();
}
static void ShowMessage (string msg) {
Console.WriteLine (msg);
}
/// <summary>
/// 發送請求(code存在byte[]的0,content存在byte[]0後面的空間裏)
/// </summary>
/// <param name="client">發送請求的socket</param>
/// <param name="requestCode">請求的編碼</param>
/// <param name="context">請求的內容</param>
static void SendMessage (Socket client, byte requestCode, string context) {
if (client != null && client.Connected) {
try {
byte[] buffer = Encoding.UTF8.GetBytes (context);
//新的請求格式buffer[0]是請求的編碼,buffe後面的內容是請求的內容
List<byte> list = new List<byte> ();
list.Add (requestCode);
list.AddRange (buffer);
byte[] newbuffer = list.ToArray ();
client.Send (newbuffer);
} catch (Exception e) {
ShowMessage (e.Message);
}
}
}
}
}
至此結束。