目录

今日良言:但行前路,不负韶华

一、Socket套接字

二、简单客户端服务器的网络通信程序


今日良言:但行前路,不负韶华

一、Socket套接字

网络编程的核心就是Socket(套接字) API,这是操作系统给应用程序提供的网络编程API.

Socekt  API 是和传输层密切相关的,在上一篇博客中,已经介绍过了,在传输层中,有两个最核心的协议: TCP / UDP,所以 Socket API 也提供了两种风格(TCP 和 UDP).这两种风格放到第二部分详细介绍.

1.TCP 和 UDP

首先简单认识一下TCP 和 UDP

UDP:无连接 不可靠传输  面向数据报  全双工

TCP:有连接  可靠传输     面向字节流  全双工 

以打电话和发短信为例:

打电话就是有连接的,需要建立连接才能进行通信,连接建立需要对方来"接受",如果连接没有建立好,通信不了.

发短信就是无连接的,直接发送即可.

可靠传输:发送方发送的数据,接收方收到与否,发送方是可以知道的.(并不是百分百传输成功)

不可靠传输:发送方发送的数据丢了就丢了,不会返回任何错误信息.

注:可不可靠和有没有连接,没有任何关系

面向数据报:数据传输是以一个个"数据报"为基本单位(一个数据报可能有若干个字节,带有一定的格式)

面向字节流:数据传输与文件读写类似,是"流式"的(一次可以读一个字节或者十个字节或者一百个字节)

全双工:一个通信通道,可以双向传输.(既可以发送,也可以接收)

以上面这根水管为例,是单向传输的,是半双工.

不妨试想一下:为什么TCP和UDP都是全双工呢? 网线不是类似于一根水管吗?

这是因为:一根网线里面,实际有8根线,所以是双全工.

二、简单客户端服务器的网络通信程序

1.基于UDP编写的简单的客户端服务器的网络通信程序

使用 UDP 套接字协议时,使用 DatagramSocket 这个类表示一个Socket对象.

在操作系统中,把这个socket对象当成是一个文件来处理的,相当于是文件描述符表上的一项.

普通的文件对应的硬件设备是:硬盘.

socket文件对应的硬件设备是:网卡.

一个socket对象,就可以和一台主机进行通信了,如果要和多个不同的主机进行通信,就需要创建多个socket对象.

DatagramSocket的构造方法:

DatagramSocket() 没有指定端口,系统会自动分配一个空闲的端口.
DatagramSocket(int port)

传入一个端口号,此时就是让当前的socket对象和这个指定的端口号关联起来.

本质上说,并不是进程和端口建立了连接,而是进程中的socket对象和端口建立了连接.

DatagramSocket的方法:

void receive(DatagramPacket p) 

这里传入的参数是一个空对象

receive方法内部会对这个空对象进行填充,

填充的内容来自网卡.

void send(DatagramPacket p) 这里的参数是一个输出型参数,一般不是空对象.
void close() 释放资源

注:这里的DatagramPocket 类 表示UDP传输中的一个报文,构造这个类的对象的时候,可以指定一些数据.

DatagramPacket 的构造方法:

DatagramPacket(byte[ ] buf,int length )

把buf这个缓冲区设置进去.

length是指定要使用的字节数的个数

DatagramPacket(byte[ ] buf,int offset,

int length,SocketAddress address)

把buf这个缓冲区设置进去.

offset是指从哪个位置开始

length是实际使用的字节数组的长度

最后这个参数表示:ip + 端口号

DatagramPacket(byte[ ]buf,int length,

InetAddress address, int port)

把buf这个缓冲区设置进去.

length是实际使用的字节数组的长度.

第三个参数是Java对IP地址的封装.

第四个参数是端口号

编写一个最简单的UDP版本的客户端服务器网络通信程序 ------ 回显服务器(echo server).

在网络通信中,一个普通的服务器的工作流程:接收请求,根据请求计算响应,返回响应.

将中间(根据请求计算响应)称为业务逻辑.

回显服务器(echo server) 省略了中间"根据请求计算响应",这里是请求是什么,响应就返回什么,对于真正的服务器而言,"根据请求计算响应",这个环境最重要,所以说,这个代码并没有实际的业务,只用来展示socket api的基本用法.

首先来看UDP版本的回显服务器的完整代码:

public class UdpEchoServer {private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动");// 服务器不是只给一个客户端提供服务 需要服务很多客户端while (true) {DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);socket.receive(requestPacket);String request = new String(requestPacket.getData(),0, requestPacket.getLength());String response = process(request);// 将响应写回客户端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);// 打印一下 当前这次请求响应的处理中间结果// 获取到requestPacket里面的ip  获取到里面的端口  请求的数据  响应的数据System.out.printf("[%s:%d] req: %s; resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);}}// 计算响应的方法public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}

针对上述代码进行分析:

private DatagramSocket socket = null;

网络编程的本质是要操作网卡,但是网卡不好直接操作,在操作系统内核中,使用了一种特殊的叫做"socket"这样的文件来抽象表示网卡.

因此,进行网络通信需要先有一个socket对象.

public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);
}

对于服务器来说,创建socket对象的同时,需要让它绑定上一个具体的端口号.

(服务器一定要关联上一个具体的端口号)

服务器是网络传输中被动的一方,如果是操作系统随机分配的端口,此时对于客户端而言,不知道当前服务器的端口是什么,就无法进行通信了.

  DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);

socket.receive(requestPacket);

接收请求并进行解析

receive方法的参数是一个"输出型参数",需要先构造好空对象(DatagramPacket 对象),然后交给receive进行填充.

String request = new String(requestPacket.getData(),0, requestPacket.getLength());

此时的DatagramPacket 是一个特殊的对象,并不好直接进行处理,可以将里面包含的数据通过getData() 方法拿出来构造成一个字符串.

通过getLength() 方法获取到实际的数据报的长度.

String response = process(request);
public String process(String request) {return request;
}

根据请求计算响应:process 方法

这里的请求和响应一样.

DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);

将响应返回客户端:

send方法的参数也是一个"输出型"参数,需要先构造这个对象.

此时构造这个对象,不能使用空字节数组了,而是需要根据响应数据来构造,同时需要通过getSocketAddress() 方法获取到客户端的ip和端口号

  System.out.printf("[%s:%d]req:%s;resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);

打印一下,当前这次请求响应的处理中间结果.四个参数依次是:

客户端的ip  客户端的端口号  请求  响应

接下来,UDP版本的回显服务器客户端的完整代码:

客户端的通信流程一般是:从控制台读取数据,构造请求并发送,接收响应并解析,显示结果

public class UdpEchoClient {private DatagramSocket socket = null;private  String serverIp = null;private int serverPort = 0;public UdpEchoClient(String serverIp,int serverPort) throws SocketException {// 让系统自动分配一个端口socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("客户端启动");Scanner scanner = new Scanner(System.in);while (true) {System.out.print("> ");String request = scanner.next();if (request.equals("exit")) {System.out.println("goodbye");break;}DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);socket.send(requestPacket);DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(),0,responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);client.start();}
}

针对上述代码进行分析:

  Socket对象
private DatagramSocket socket = null;服务器ip地址
private  String serverIp = null;服务器端口号
private int serverPort = 0;
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {// 让系统自动分配一个端口socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;
}

一次网络通信,需要有两个IP    两个端口

由于当前客户端和服务器都在同一个主机上,因此IP都是是127.0.0.1(环回IP)

客户端的端口号让系统分配即可,不能指定客户端的端口号,客户端如果显式指定端口,可能就和客户端电脑上的其他程序的端口冲突了,这一冲突就导致,程序无法进行通信了.

服务器的端口也要告诉客户端,才能顺利进行通信.

为什么服务器这里指定端口不怕重复呢?

因为:服务器是程序猿手里的机器,上面运行什么,程序猿都是可控的.程序猿可以安排哪个程序用哪个端口.

System.out.print("> ");
String request = scanner.next();
if (request.equals("exit")) {System.out.println("goodbye");break;
}

从控制台读取要发送的数据:

DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);

构造请求并发送:

构造这个UDP请求的时候,需要将服务器的ip和端口号都要传过来,但是此处的ip地址需要一个32位的整数形式,上述的serverIp是一个字符串,需要使用 InetAddress.getByName()  进行转换

DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength());

接收响应并解析:

通过receive方法,传入一个空对象,读取服务器的UDP响应,然后将读取到的内容进行解析,构造成字符串

System.out.println(response);

把解析好的结果显示出来

上述UDP版本的回显服务器和客户端的工作流程:

1.先启动服务器 服务器运行到receive() 这里阻塞:

2.客户端读取用户输入的内容:

3.客户端发送请求:

4.服务器收到请求:

5.服务器根据请求计算响应:

6.服务器发送响应:

7.客户端的receive从阻塞中返回,读取到了响应.,然后解析打印.

运行结果展示:

启动服务器:

启动客户端:

客户端输入哈喽,返回响应内容哈喽:

观察此时服务器端打印的相关信息:

对于客户端服务器来说,一个服务器需要给很多个客户端提供服务,所以说,我们需要构造很多个客户端来进行测试,但是IDEA默认只能启动一个客户端,需要调整一下,让idea能启动多个客户端.

如下图:

此时,我们再次运行,观察结果:

构造多个客户端只需要多执行几次原来客户端的代码即可,这里启动2个客户端

客户端1

客户端2

针对上述程序,来观察一下这里"端口冲突"的效果

注:

对于上面的服务器来说,这里的 while 是死循环,这样的死循环在服务器程序中是没什么问题的,因为服务器一般都是 7 * 24小时运行的.

如果有很多个客户端,并且每个客户端的请求都发的很快,此时,上述代码中的while 循环

就无法及时进行处理.

"高并发"指的就是这个意思:客户端请求太多,处理不过来了.

如何处理高并发呢?

a.多线程,可以更充分的调用计算机的硬件资源.

b.多加机器.管理成本提高.

2.基于TCP编写的简单的客户端服务器的网络通信程序

上面是UDP版本的回显服务器,接下来实现TCP版本的回显服务器.

使用 TCP 套接字协议 时,主要是两个类:

ServerSocket  专门给服务器使用的Socket对象.

Socket 既会给客户端使用,也会给服务器使用.

TCP是以字节的方式,流式传输的,不是以数据报为单位进行传输的,所以并不需要像UDP那样,使用一个类表示"TCP数据报".

ServerSocket 类:

ServerSocket 的构造方法:

ServerSocket(int port)

创建一个服务端流套接字Socket对象,

并绑定到指定端口

ServerSocket 的方法;

Socket accept()

有客户端连接后,返回一个服务器Socket对象

并基于这个Socket对象建立与客户端的连接,否则阻塞等待

Socket 类:

在服务器这里是通过accept方法返回得到的.

在客户端这里,通过代码构造,构造的时候指定一个服务器的IP和端口号,有了这个信息就能和服务器建立连接了.

Socket 的构造方法:

Socket(String serverIp,int port) 传入服务器的IP和端口号

Socket 的方法:

InputStream getInputStream()

通过Socket对象,获取到内部的流对象

借助流对象进行发送/接收

OutputStream getOutputStream

通过Socket对象,获取到内部的流对象

借助流对象进行发送/接收

TCP版本的回显服务器的完整代码:

public class TcpEchoServer {private ServerSocket socket = null;public TcpEchoServer(int port) throws IOException {socket = new ServerSocket(port);}// 启动服务器的方法public void start() throws IOException {System.out.println("启动服务器");while (true) {Socket clientSocket = socket.accept();// 通过这个方法来交流processConnection(clientSocket);}}// 使用这个方法来处理一个连接// 这一个连接对应到一个客户端,这里可能涉及到多次交互private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// 处理多次请求和响应while (true) {// 获取请求并解析Scanner scanner = new Scanner(inputStream);if (!scanner.hasNext()) {System.out.printf("[%s:%d]  客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());break;}String request = scanner.next();// 根据请求构造响应String response = process(request);// 返回客户端// 通过字符流转换一下PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();System.out.printf("[%s:%d], req:%s; resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();} finally {// 保证一定执行close操作try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}// 根据请求计算响应public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

针对上述代码进行分析:

    private ServerSocket socket = null;
    public TcpEchoServer(int port) throws IOException {
        socket = new ServerSocket(port);
    }

先创建一个ServerSocket对象,然后通过构造方法绑定一个端口.

  while (true) {
            Socket clientSocket = socket.accept();
            // 通过这个方法来交流
            processConnection(clientSocket);
        }

为多个客户端提供服务,使用clientSocket和具体的客户端进行交流,一个客户端创建一个

accpet的效果是"接收连接",前提是得有客户端来建立连接.

客户端在构造Socket对象的时候,就会指定服务器的端口和IP,如果没有客户端建立连接,此时

accept就会阻塞.

try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {

} }catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 保证一定执行close操作
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

在之前的博客中已经介绍过上述语法:try with resource,不是随便一个对象放到try中就被自动释放 ,只有 实现了Closeable 这个接口的类才可以放到 try()中自动关闭,这个接口提供的就是close() 方法.(这里关闭流对象)

在当前代码中,用到一个clientSocket,此时一个任意一个客户端连接上来,都会返回/创建一个Socket对象(Socket就是文件), 每次创建一个clientSocket对象,就要占用一个文件描述符表的位置,因此在使用完毕后,需要进行"释放",finally确保一定执行close操作.

与之前UDP版本的回显服务器的代码进行比较,会发现,前面的Socket对象没有释放.

一方面是因为UDP代码中的socket对象生命周期更长(跟随整个程序),另一方面,这些socket对象也不多,是固定数量.但是TCP代码中的clientSocket数量多,每个客户端都有一个,生命周期也更短,因此必须执行close操作.

    Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {   

   System.out.printf("[%s:%d]  客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }

    String request = scanner.next();

获取请求并解析

通过流对象进行操作,if中的条件是判断是否有下一个数据,如果没有,说明当前客户端已经结束请求,退出循环即可.

  String response = process(request);

  // 根据请求计算响应
    public String process(String request) {
        return request;
    }

根据请求计算响应:

 PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d], req:%s; resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);
            }

返回响应:

由于outputStream的write方法并不能直接发送String数据,所以说,可以通过两种方法进行发送:

1).将String转换成字节数组,然后进行写入

2).通过字符流进行转换,然后发送.

printWriter.println(response);

这里使用printWriter的println方法来发送数据,这个方法会在发送的数据后面自动带上 \n 换行,方便客户端解析.

使用flush方法刷新缓冲区,确保当前写入的数据已经发送出去了.

printWriter还有一个print方法(不带\n换行),如果代码使用这个方法是否可行?

答案是不行!!. Tcp协议是面向字节流的协议,接收方如何知道一次操作要读多少个字节,就需要在数据传输中进行明确的约定,此处代码隐式约定了使用 \n 来作为当前代码的请求/响应分割约定.

接下来,TCP版本的回显服务器客户端的完整代码:

public class TcpEchoClient {private Socket socket = null;// 需要告诉客户端 服务器的ip和端口public TcpEchoClient(String serverIp,int port) throws IOException {socket = new Socket(serverIp,port);}public void start() {System.out.println("客户端启动!!");Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {while (true) {// 1. 从控制台读取请求System.out.print("> ");String request = scanner.next();if (request.equals("exit")) {System.out.println("goodby");break;}// 2.把读到的内容构造成请求 发送给服务器PrintWriter printWriter = new PrintWriter(outputStream);// 通过println方法发送printWriter.println(request);// 保证数据发送出去了printWriter.flush();// 3.读取服务器的响应Scanner responScan = new Scanner(inputStream);String response = responScan.next();// 4.把响应的内容显示到界面上System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();}
}

针对上述代码进行分析:

public TcpEchoClient(String serverIp,int port) throws IOException {
        socket = new Socket(serverIp,port);
    }

Socket构造方法,可以识别点分十进制的IP地址,所以不需要转换.

当传入服务器的IP和端口号,new这个对象的同时就会进行TCP连接操作.

PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();

通过字符流转换,然后将客户端的请求发送出去.

注:编写TCP的客户端服务器程序的时候,涉及到"长连接""短连接"问题:

长连接:客户端建立连接之后,不着急断开连接,发送请求,读取响应;再发送请求,读取响应;若干轮之后,客户端确实短时间内不再需要这个连接了,此时再断开.

短连接:客户端每次给服务器发送消息,先建立连接,发送请求,读取响应,关闭连接;下次再发送,则需要重新建立连接.

UDP是无连接的,不涉及长短连接的问题.

运行结果展示:

启动服务器:

启动客户端:

客户端输入哈喽,返回响应内容哈喽:

观察此时服务器打印的相关信息:

客户端输入exit下线:

通过构造多个客户端来继续运行(构造多个客户端的方法和之前UDP版本方法相同)

此时,我们再次运行,观察结果:

构造多个客户端只需要多执行几次原来客户端的代码即可,这里启动2个客户端:

当启动两个客户端的时候,会发现,在服务器这里只提示只有一个客户端上线,也就是最先启动的客户端.

然后两个客户端分别发送信息,观察响应以及服务器:

客户端1:

客户端2:

服务器:

此时会发现,只有客户端1和服务器进行通信,客户端2并没有与服务器进行通信,这是为什么呢?

其实原因出在服务器的代码中:上述服务器的代码实现,决定了一次只能处理一个客户端.

当客户端1退出后,会发现客户端2可以进行通信了:

这里通过代码再解释一下:

一个服务器一次只能与一个客户端进行通信,这显然是不行的,所以说,如何解决上述问题呢?

多线程. 让主线程负责执行accept,每次收到一个客户端连接,就创建新线程,让新线程负责处理这个客户端.

替换之前服务器的代码:

此时启动两个客户端再次运行观察结果:

服务器:

客户端1:

客户端2:

此时就解决了上述问题.

但是使用多线程带来的开销可能比较大,所以说,使用线程池会更好一点.

替换多线程的代码:

虽然使用了线程池, 但是还不够,如果客户端非常多,而且客户单迟迟不断开连接,此时就会导致当前的机器上有很多线程.

如果一个服务器有几千个客户端,就有几千个线程,有几万个客户端,就有几万个线程...

这对于我们的机器而言,是一个很大的负担.

如何解决上述问题(解决单机支持更大量客户端)呢?

这就是 C10M问题:

C10K问题:单机处理1w个客户端.

C10M问题:单机处理1kw个客户端(并不是说单机真的处理1kw,而是表达说客户端比1w还多..)

是否有办法解决上述问题?

IO多路复用(IO多路转接):让一个线程,处理很多个客户端连接.

给这个线程安排一个集合,这个集合就放很多连接,这个线程就负责监听这个集合,哪个连接有数据来了,线程就处理哪个连接.

在操作系统里面,提供了一些原生的API select poll  epoll.

在java中,提供了一组NIO 类,封装了上述的多路复用的API.

简单客户端服务器网络通信程序相关推荐

  1. java打开客户端程序_Java客户端服务器应用程序 - 已在使用的地址:connect

    我目前正在开发一个简单的多人游戏,其中有几个客户需要连接到服务器 . 我的服务器由一个serverSocket组成 . 此serverSocket接受传入连接并将其交给启动单独线程的连接对象 . Se ...

  2. 点对点 客户端-服务器 聊天程序

    服务器,客户端都是父进程.子进程分别负责发送.接收字符串. 另外使用了信号函数来发送和接收处理信号,比如当子进程结束时,传递一个信号给父进程,然后父进程会执行信号处理函数. 服务器端: 1 #incl ...

  3. qt客户端显示服务器发送的图片,c++ - Qt客户端服务器应用程序“发送图像”出现问题 - 堆栈内存溢出...

    我正在尝试通过QDataStream从客户端向服务器发送图像( OpenCV Mat ). 第一项是int,即缓冲区的大小. 它可以处理10到15张图片,然后服务器读取第一个int随机数(通常〜2 ^ ...

  4. cov/cor中有遗漏值_协调遗漏的效果–使用简单的NIO客户端/服务器测量回送延迟...

    cov/cor中有遗漏值 在这篇文章中,我演示了许多想法和技术: 如何编写一个简单的非阻塞NIO客户端/服务器 协同遗漏的影响 如何测量百分位数的延迟(相对于简单平均) 如何在计算机上计时延迟回送 我 ...

  5. 协同遗漏的效果–使用简单的NIO客户端/服务器测量回送延迟

    在这篇文章中,我演示了许多想法和技术: 如何编写一个简单的非阻塞NIO客户端/服务器 协调遗漏的影响 如何测量百分位数的延迟(相对于简单平均) 如何在计算机上计时延迟回送 我最近正在为客户端服务器应用 ...

  6. 可以创建专业的客户端/服务器视频会议应用程序的音频和视频控件LEADTOOLS Video Conferencing SDK...

    LEADTOOLS Video Streaming Module控件为您创建一个自定义的视频会议应用程序和工具提供所有需要的功能.软件开发人员可以使用Video Streaming Module SD ...

  7. 使用TDI与WinSock进行客户端服务器编程

    简介: 在本文中,您将了解到使用传输驱动程序接口TDI与应用层套接字WinSock客户端服务器应用程序内核级编程实现细节.介绍常用的TDI函数并提供编写TDI与WinSock(TCP)应用程序的详细说 ...

  8. C++基于TCP/IP简单的客户端、服务器通信程序实例

    本篇文章实现了一个基于TCP 的一个非常简单的客户/服务器通信程序实例.该程序中通讯协议使用的是面向连接的TCP协议SOCK_STREAM, 服务器的ip地址为本地地址即: 127.0.0.1,端口号 ...

  9. hosts多个ip对应一个主机名_一个简单的Web应用程序,用作连接到ssh服务器的ssh客户端...

    WebSSH 一个简单的Web应用程序,用作连接到ssh服务器的ssh客户端.它是用Python编写的,基于tornado,paramiko和xterm.js. 特征 支持SSH密码验证,包括空密码. ...

最新文章

  1. oom 如何避免 高并发_【转载】如何避免OOM?看Greenplum的最佳实践
  2. 软件架构设计 温昱著 - 读书笔记
  3. 01,完全,多重,分组
  4. 锻造「明星产品」的艺术与科学,在于取舍【附乔布斯张小龙的产品设计原则】...
  5. python实现蜘蛛功能批量下载手机壁纸
  6. 反写规则-销售订单关闭后不允许出库 (销售订单-销售出库单)
  7. 简述python解释器的作用_什么是python解释器?
  8. linux 安装mysql5.7.12_Linux系统上安装mysql5.7.12
  9. 研究称:苹果开始感受到全球芯片短缺影响,但三星等受影响更大
  10. 雷军:电视机越大才越舒服!
  11. resnet101网络结构
  12. 7-28 猴子选大王 (20分)
  13. 华为外包软件公司集体罢工!
  14. zabbix基础·配置短信报警
  15. 网络运维python之NETCONF--协程gevent+ncclient,2分钟巡检几千台华为CE交换机
  16. python子图标题_python, 如何在subplot在总的图画上面加title?
  17. python绘制花朵图案_Python编写万花尺图案实例
  18. 用python爬取智联招聘
  19. 一文了解如何使用移动应用安全组件Soot和Flowdroid
  20. 计算机与现代教育的英语作文,雅思写作高分范文:电脑是否现代教育所必须?...

热门文章

  1. C#实现软件注册码算法
  2. 【成长】程序员的成长学习笔记(长更)
  3. 9月6号 网工学习 数字签名
  4. 1.2描述性统计-离散程度
  5. SQL INSERT INTO SELECT 语句
  6. 查询AD用户最后具体登陆时间
  7. php滚动条怎么用,如何让滚动条自动滚到最底部
  8. Ubuntu通过邮件自动发送IP地址
  9. 视频识别之PC版车牌识别sdk
  10. TP6.0 调试模式下关闭Trace调试