C# TCP 聊天服务器:连接仅以一种方式工作

Posted

技术标签:

【中文标题】C# TCP 聊天服务器:连接仅以一种方式工作【英文标题】:C# TCP Chat-Server: Connection only works one way 【发布时间】:2014-06-16 21:38:23 【问题描述】:

我的主笔记本电脑和另一台笔记本电脑上都有通用 TCP 客户端和 TCP 侦听器设置。客户端和侦听器是他们自己的独立程序,因此我在两台笔记本电脑上都运行了一个客户端和服务器,以尝试让一个简单的即时通讯工具运行。

    如果两个程序都在同一台笔记本电脑上执行,客户端和服务器将愉快地通信。这对于我的主笔记本电脑和其他笔记本电脑都是如此。

    我的主笔记本电脑的客户端将愉快地向我的另一台笔记本电脑发送消息,而另一台笔记本电脑将优雅地接收来自我的主笔记本电脑的消息 笔记本电脑。

    但是,当我的另一台笔记本电脑的客户端向我的主笔记本电脑的服务器发送消息时,它会收到与超时相关的通信失败类型的错误。它只是不发送消息。

Error message (caught by try-catch); "连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立连接失败,因为连接的主机没有响应我的 IP:端口号。”

我得到的 IP 和端口号是正确的,所以排除它。 没有防火墙业务,因为另一台笔记本电脑根本不关心从我的主笔记本电脑接收消息。

我还尝试了随机端口号,并确保我的主笔记本电脑上的客户端正在通过我的另一台笔记本电脑的服务器正在侦听(或接受来自)的相同端口号进行连接。

那么为什么我的另一台笔记本电脑没有成功向我的主笔记本电脑发送消息?

客户端要求用户输入 IP,然后输入端口号。然后它等待用户输入消息,然后通过给定的端口号连接并将该消息发送到 IP。

服务器要求用户输入端口号并打印通过该端口接收到的消息。

这是客户端程序的代码;

static void Main(string[] args)
    
        string IP = localIPAddress();
        Console.WriteLine("Your IP: " + IP); //provides IP number
        Console.Write("Enter IP to connect to: ");
        string connectToIP = Console.ReadLine();
        if (connectToIP == "self")
        
            connectToIP = localIPAddress(); 
            // if I want to test both on the same computer
        
        Console.WriteLine("\nEnter port number: ");
        int portNo = int.Parse(Console.ReadLine());

        while (true)
        
            string message = Console.ReadLine();
            try
            
                // connection doesn't begin until ReadLine is done
                request(connectToIP, portNo, message); 
            
            catch (Exception e)
            
                Console.WriteLine(e.Message);
            
        
    

    public static string localIPAddress()
    
        IPHostEntry host;
        string localIP = "?";
        host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (IPAddress ip in host.AddressList)
        
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            
                localIP = ip.ToString();
            
        
        return localIP;
    

    static void request(string address, int port, string message)
    
        TcpClient client = new TcpClient();

        client.SendTimeout = 1000;
        client.ReceiveTimeout = 1000;

        try
        
            client.Connect(address, port);
            StreamWriter sw = new StreamWriter(client.GetStream());

            sw.WriteLine(message);

            sw.Flush();

            sw.Close();
        
        catch (Exception a)
        
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(a.Message.ToString());
            Console.ResetColor();
        
    

这是服务器的代码;

static void Main(string[] args)
    
        Console.WriteLine("Your IP: " + localIPAddress());
        Console.Write("Enter port number you're receiving from: ");
        int portNo = int.Parse(Console.ReadLine());

        TcpListener listener = new TcpListener(IPAddress.Any, portNo);
        Socket connection;
        NetworkStream socketStream;
        listener.Start();
        while (true)
        
            connection = listener.AcceptSocket();
            connection.SendTimeout = 1000;
            connection.ReceiveTimeout = 1000;
            socketStream = new NetworkStream(connection);
            try
            
                respond(socketStream);
            
            catch(Exception e)
            
                Console.WriteLine(e.Message);
            
            finally
            
                socketStream.Close();
                connection.Close();
            
        
    

    public static string localIPAddress()
    
        IPHostEntry host;
        string localIP = "?";
        host = Dns.GetHostEntry(Dns.GetHostName());
        foreach (IPAddress ip in host.AddressList)
        
            if (ip.AddressFamily == AddressFamily.InterNetwork)
            
                localIP = ip.ToString();
            
        
        return localIP;
    

    static void respond(NetworkStream strm)
    
        List<string> sentIn = new List<string>();
        StreamReader sr = new StreamReader(strm);

        while (sr.Peek() != -1)
            sentIn.Add(sr.ReadLine());

        sr.Close();

        foreach (string s in sentIn)
        
            Console.WriteLine(s);
        
    

我的代码有问题吗?当任何一台笔记本电脑使用这些程序时,都没有与防火墙相关的消息。

它可能是客户端的 sw.Flush(),因为它曾经导致发送过程冻结。

提前致谢。一旦我解决了这个问题,我就会开始想知道如何使用它来制作多人 XNA 游戏。

【问题讨论】:

【参考方案1】:

您可能想尝试将超时时间延长一点(或将它们全部删除;它们给我带来了问题)。此外,在您处理主线程上的发送时创建一个线程来接收消息确实是个好主意。

一些注意事项:

如果您想连接到本地 IP,可以使用“环回”或“127.0.0.1”

if (connectToIP == "self")

    connectToIP = localIPAddress(); 
    // if I want to test both on the same computer

你真的不应该连接,发送一条消息,然后断开连接,只是为了再次连接。

为客户尝试这样的事情:

using System.Threading;

    static void Main(string[] args)
    
        TcpClient client = new TcpClient();

        Console.Write("IP: ");
        string ip = Console.ReadLine();
        Console.Write("Port: ");
        int port = int.Parse(Console.ReadLine());

        try
        
            client.Connect(ip, port);
            StreamWriter sw = new StreamWriter(client.GetStream());
            sw.AutoFlush = true;
            StreamReader sr = new StreamReader(client.GetStream());
            Thread readThread = new Thread(() => readSocket(sr));
            readThread.Start(); //Run readSocket() at the same time
            string message = "";
            while (message != "exit")
            
                message = Console.ReadLine();
                sw.WriteLine(message);
            
            client.Close();
            return;                
        
        catch (Exception e)  Console.WriteLine(e.ToString()); 
    

    static void readSocket(StreamReader sr)
    
        try
        
            string message = "";
            while ((message = sr.ReadLine()) != null)
            
                Console.WriteLine(message);
            
        
        catch (System.IO.IOException)  /*when we force close, this goes off, so ignore it*/ 
        catch (Exception e)
        
            Console.WriteLine(e.ToString());
        
    

这是一个服务器示例:

    static void Main(string[] args)
    
        Console.Write("Port: ");
        int port = int.Parse(Console.ReadLine());
        TcpListener server = new TcpListener(IPAddress.Any, port);

        try
        
            server.Start();
            TcpClient client = server.AcceptTcpClient();
            StreamWriter sw = new StreamWriter(client.GetStream());
            sw.AutoFlush = true;
            StreamReader sr = new StreamReader(client.GetStream());
            Thread readThread = new Thread(() => readSocket(sr));
            readThread.Start();
            string message = "";
            while (message != "exit")
            
                message = Console.ReadLine();
                sw.WriteLine(message);
            
            client.Close();
            return;                
        
        catch (Exception e)  Console.WriteLine(e.ToString()); 
    

    static void readSocket(StreamReader sr)
    
        try
        
            string message = "";
            while ((message = sr.ReadLine()) != null)
            
                Console.WriteLine(message);
            
        
        catch (System.IO.IOException)  /*when we force close, this goes off, so ignore it*/ 
        catch (Exception e)
        
            Console.WriteLine(e.ToString());
        
    

这是一个异步服务器的代码,它将消息转发到所有连接的客户端(如果您担心无法共享端口)。

我试图让这些示例保持简单,但为所有示例添加更多异常处理可能是个好主意。

class Server

    private int port;
    private Socket serverSocket;
    private List<StateObject> clientList;
    private const int DEFAULT_PORT = 1338;

    public Server()
    
        this.port = 1338; //default port
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientList = new List<StateObject>();
    

    public Server(int port)
    
        this.port = port;
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientList = new List<StateObject>();
    

    public void startListening(int port = DEFAULT_PORT)
    
        this.port = port;
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, this.port));
        serverSocket.Listen(1);
        serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
    

    private void AcceptCallback(IAsyncResult AR)
    
        try
        
            StateObject state = new StateObject();
            state.workSocket = serverSocket.EndAccept(AR);
            //Console.WriteLine("Client Connected");
            clientList.Add(state);
            state.workSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
        
        catch  
    

    private void ReceiveCallback(IAsyncResult AR)
    
        StateObject state = (StateObject)AR.AsyncState;
        Socket s = state.workSocket;
        try
        
            int received = s.EndReceive(AR);

            if (received == 0)
                return;

            if (received > 0)
                state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, received));

            string content = state.sb.ToString();

            if (content.IndexOf(Environment.NewLine) > -1)
            
                //Console.WriteLine(content);
                foreach (StateObject others in clientList)
                    if (others != state)
                        others.workSocket.Send(Encoding.ASCII.GetBytes(content.ToCharArray()));
                state.sb.Clear();
            

            Array.Clear(state.buffer, 0, StateObject.BufferSize);
            s.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
        
        catch (Exception huh) 
            s.Close();
            s.Dispose();
            //Console.WriteLine("Client Disconnected");
            clientList.Remove(state);
            return;
        
    
    class StateObject
    
        public Socket workSocket = null;
        public const int BufferSize = 1024;
        public byte[] buffer = new byte[BufferSize];
        public StringBuilder sb = new StringBuilder();
    

【讨论】:

第三次编辑:好的,它现在可以工作了,但他们必须使用不同的端口。我之前尝试过线程但无济于事,但只要笔记本电脑自己的客户端和它连接的服务器不共享端口号,您的代码就可以工作。 好的,另外一台笔记本的客户端有可能会无缘无故关闭,也会发消息失败。这似乎比它需要的更复杂。 我不确定您到底遇到了什么问题。您只能在一个端口上运行一台服务器,并且在此设置中它只能处理一个连接。如果您试图让它们长时间保持连接,您可以添加一些代码以在遇到异常时重新连接。如果您对此感兴趣,我将添加一个异步服务器的示例,但我试图使这个示例尽可能简单。如果你想捕捉错误并让它重新连接,你可以添加它。这应该是捕捉异常,断开(客户端和服务器)并重新连接的问题。 另外,别忘了我基本上忽略了他们两个上的 system.io 异常。这可能是最常见的异常之一,所以如果它“无缘无故”地停止,可能是因为读/写出错并且异常被忽略了。您可以更改此行以显示异常:catch (System.IO.IOException) /*当我们强制关闭时,它会关闭,所以忽略它*/

以上是关于C# TCP 聊天服务器:连接仅以一种方式工作的主要内容,如果未能解决你的问题,请参考以下文章

Qt5 多线程:信号仅以一种方式工作

python SQLAlchemy多对多唯一约束仅以一种方式工作

SwiftUImatchedGeometryEffect 仅以一种方式设置动画

C#多线程聊天服务器,处理断开连接

NSSocketPort 端口只能以一种方式工作

求一个C#最简单的TCP传输信息例子(实现聊天和传送文件,再简单点实现聊天也行)