LINUX下如何创建TCP客户端和服务器,实现通信

LINUX下如何创建TCP客户端和服务器,实现通信,第1张

1可能是在获取客户端的ip和端口时,处理出现问题,导致无法正确发送到客户端。
2客户端是否使用固定的端口来接收服务器信息,或服务器是否正确发送到客户端的相应的端口。
3通过上面分析,最大可能是在处理端口出现问题,请重新检查。
4实在不行,最好使用抛出异常方法来捕获错误消息,或是通过一步一步调试分析数据发送过程。

你了解TCP/IP socket编程相关知识吗?

网页链接

首先你要在云服务器上运行一个服务器程序,然后在本机运行客户端程序,两者通过TCP协议通讯交换数据(即你所说的连上云服务器)。

最简单的服务器程序:

using System;
using SystemIO;
using SystemNet;
using SystemNetSockets;
using SystemText;
class MyTcpListener
{
  public static void Main()
  { 
    TcpListener server=null;   
    try
    {
      // Set the TcpListener on port 13000
      Int32 port = 13000;
      IPAddress localAddr = IPAddressParse("127001");
      
      // TcpListener server = new TcpListener(port);
      server = new TcpListener(localAddr, port);
      // Start listening for client requests
      serverStart();
         
      // Buffer for reading data
      Byte[] bytes = new Byte[256];
      String data = null;
      // Enter the listening loop
      while(true) 
      {
        ConsoleWrite("Waiting for a connection ");
        
        // Perform a blocking call to accept requests
        // You could also user serverAcceptSocket() here
        TcpClient client = serverAcceptTcpClient();            
        ConsoleWriteLine("Connected!");
        data = null;
        // Get a stream object for reading and writing
        NetworkStream stream = clientGetStream();
        int i;
        // Loop to receive all the data sent by the client
        while((i = streamRead(bytes, 0, bytesLength))!=0) 
        {   
          // Translate data bytes to a ASCII string
          data = SystemTextEncodingASCIIGetString(bytes, 0, i);
          ConsoleWriteLine("Received: {0}", data);
       
          // Process the data sent by the client
          data = dataToUpper();
          byte[] msg = SystemTextEncodingASCIIGetBytes(data);
          // Send back a response
          streamWrite(msg, 0, msgLength);
          ConsoleWriteLine("Sent: {0}", data);            
        }
         
        // Shutdown and end connection
        clientClose();
      }
    }
    catch(SocketException e)
    {
      ConsoleWriteLine("SocketException: {0}", e);
    }
    finally
    {
       // Stop listening for new clients
       serverStop();
    }
      
    ConsoleWriteLine("\nHit enter to continue");
    ConsoleRead();
  }   
}

最简单的客户端:

static void Connect(String server, String message) 
{
  try 
  {
    // Create a TcpClient
    // Note, for this client to work you need to have a TcpServer 
    // connected to the same address as specified by the server, port
    // combination
    Int32 port = 13000;
    TcpClient client = new TcpClient(server, port);
    
    // Translate the passed message into ASCII and store it as a Byte array
    Byte[] data = SystemTextEncodingASCIIGetBytes(message);         
    // Get a client stream for reading and writing
    //  Stream stream = clientGetStream();
    
    NetworkStream stream = clientGetStream();
    // Send the message to the connected TcpServer 
    streamWrite(data, 0, dataLength);
    ConsoleWriteLine("Sent: {0}", message);         
    // Receive the TcpServerresponse
    
    // Buffer to store the response bytes
    data = new Byte[256];
    // String to store the response ASCII representation
    String responseData = StringEmpty;
    // Read the first batch of the TcpServer response bytes
    Int32 bytes = streamRead(data, 0, dataLength);
    responseData = SystemTextEncodingASCIIGetString(data, 0, bytes);
    ConsoleWriteLine("Received: {0}", responseData);         
    // Close everything
    streamClose();         
    clientClose();         
  } 
  catch (ArgumentNullException e) 
  {
    ConsoleWriteLine("ArgumentNullException: {0}", e);
  } 
  catch (SocketException e) 
  {
    ConsoleWriteLine("SocketException: {0}", e);
  }
    
  ConsoleWriteLine("\n Press Enter to continue");
  ConsoleRead();
}

unix的哲学是一切皆文件,可以把socket看成是一种特殊的文件,而一些socket函数就是对其进行的 *** 作api(读/写IO、打开、关闭)。我们知道普通文件的打开 *** 作(open)返回一个文件描述字,与之类似,socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,

 sockfd即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    在将一个地址绑定到socket的时候,需要先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过不少血案,谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。
    这里的主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

   listen函数的第一个参数即为要监听的socket描述字,第二个参数为socket可以接受的排队的最大连接个数。listen函数表示等待客户的连接请求。

  connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

 TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数去接收请求,这样连接就建立好了(在connect之后就建立好了三次连接),之后就可以开始进行类似于普通文件的网络I/O *** 作了。

 如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与客户的TCP连接。
 accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

 read函数是负责从fd中读取内容当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

 write函数将buf中的nbytes字节内容写入文件描述符fd成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误

 在服务器与客户端建立连接之后,会进行一些读写 *** 作,完成了读写 *** 作就要关闭相应的socket描述字,类似于 *** 作完打开的文件要调用fclose关闭打开的文件。

 close一个TCP socket的缺省行为时把该socket标记为已关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数

 close *** 作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

 我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

客户端向服务器发送一个SYN J

服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1

客户端再想服务器发一个确认ACK K+1

socket中TCP的四次握手释放连接详解

 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。一段时间之后,服务端调用close关闭它的socket。这导致它的TCP也发送一个FIN N;接收到这个FIN的源发送端TCP对它进行确认,这样每个方向上都有一个FIN和ACK。

为什么要三次握手

由于tcp连接是全双工的,存在着双向的读写通道,每个方向都必须单独进行关闭。当一方完成它的数据发送任务后就可以发送一个FIN来终止这个方向的连接。收到FIN只意味着这个方向上没有数据流动,但并不表示在另一个方向上没有读写,所以要双向的读写关闭需要四次握手,

    3 time_wait状态如何避免?

首先服务器可以设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIME_WAIT状态时可以重用端口。在一个非常有用的场景就是,如果你的服务器程序停止后想立即重启,而新的套接字依旧希望使用同一端口,此时SO_REUSEADDR选项就可以避免TIME_WAIT状态。

1客户端连接服务器的80服务,这时客户端会启用一个本地的端口访问服务器的80,访问完成后关闭此连接,立刻再次访问服务器的
80,这时客户端会启用另一个本地的端口,而不是刚才使用的那个本地端口。原因就是刚才的那个连接还处于TIME_WAIT状态。

2客户端连接服务器的80服务,这时服务器关闭80端口,立即再次重启80端口的服务,这时可能不会成功启动,原因也是服务器的连
接还处于TIME_WAIT状态。
实战分析:

状态描述:

CLOSED:无连接是活动的或正在进行
LISTEN:服务器在等待进入呼叫
SYN_RECV:一个连接请求已经到达,等待确认
SYN_SENT:应用已经开始,打开一个连接
ESTABLISHED:正常数据传输状态
FIN_WAIT1:应用说它已经完成
FIN_WAIT2:另一边已同意释放
ITMED_WAIT:等待所有分组死掉
CLOSING:两边同时尝试关闭
TIME_WAIT:另一边已初始化一个释放
LAST_ACK:等待所有分组死掉</pre>

命令解释:

如何尽量处理TIMEWAIT过多

编辑内核文件/etc/sysctlconf,加入以下内容:
netipv4tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
netipv4tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
netipv4tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
netipv4tcp_fin_timeout 修改系默认的 TIMEOUT 时间</pre>

然后执行 /sbin/sysctl -p 让参数生效
/etc/sysctlconf是一个允许改变正在运行中的Linux系统的接口,它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,修改内核参数永久生效。

简单来说,就是打开系统的TIMEWAIT重用和快速回收。

  本文主要讲述了socket的主要api,以及tcp的连接过程和其中各个阶段的连接状态,理解这些是更深入了解tcp的基础!

进行以下几个步骤:
1、设置APN接入点:在4G模块中设置正确的APN接入点,以便能够正常接入网络。
2、配置拨号参数:将4G模块与电信平台建立连接需要配置相应的拨号参数,例如用户名、密码等。
3、设定服务器地址和端口号:在4G模块中设置连接的服务器地址和端口号,以便能够与目标设备进行通信。
4、建立TCP连接:在4G模块上建立与目标服务器的TCP连接,通过指定的协议、端口号和数据格式实现双向数据传输。

首先回答第一个问题,如果游戏本a设置全局代理到拥有公网IP的云服务器b,并在b上建立代理服务,则a的游戏服务器可以通过b的公网IP地址访问。但是,这并不意味着a的游戏服务器拥有公网IP地址。如果要让a的游戏服务器拥有公网IP地址,需要在云服务器b上建立端口映射或NAT转发等服务,将b的公网IP映射到a的游戏服务器上。
其次回答第二个问题,要让a的游戏服务器出现在steam服务器列表中,需要使用Steamworks SDK并在a的游戏服务器上实现相关功能。具体来说,需要在游戏服务器上使用Steamworks SDK提供的API,将a的游戏服务器注册到Steam服务器列表中。需要注意的是,Steamworks SDK只能用于Steam游戏。
关于第三个问题,如果使用樱花frp的TCP/UDP隧道,需要建立的隧道端口应该包括a的游戏服务器端口以及frp客户端与服务端通信所使用的端口。具体来说,需要在游戏本a上运行frp客户端,并通过frp客户端将a的游戏服务器端口映射到云服务器b上。同时,需要在云服务器b上运行frp服务端,并将b的公网IP地址与frp服务端通信所使用的端口映射到frp客户端所使用的端口上。这样,通过b的公网IP地址加上映射的端口即可访问到a的游戏服务器。
最后,如果不使用樱花frp而是在云服务器b上建立其他服务,可以考虑使用端口映射或NAT转发等服务,将b的公网IP映射到a的游戏服务器上。具体来说,可以在云服务器b上运行端口映射或NAT转发软件,将b的公网IP地址与a的游戏服务器端口进行映射。这样,通过b的公网IP地址加上映射的端口即可访问到a的游戏服务器。需要注意的是,端口映射或NAT转发可能会对网络安全造成一定的影响,需要谨慎使用。

手机是无法直接连接TCP服务器的,没有这方面的APP,也没有听说过通过蓝牙能够连接tcp服务器的,剩下唯一的方法就是通过外接数据线去连接服务器了,但就算连接好了,具体的 *** 作还是需要专业程序员去处理的


欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zz/13453653.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-08-09
下一篇 2023-08-09

发表评论

登录后才能评论

评论列表(0条)

保存