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 服务端
代码语言:javascript复制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 客户端
代码语言:javascript复制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)
}
}
2.2 客户端
客户端方面有一点不同,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, "rn")
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]))
}
}