精华内容
下载资源
问答
  • 在使用套接字进行网络编程时
    千次阅读
    2021-11-21 21:48:49

    一:TCP协议的传输特点

    1.TCP协议是一个面向连接的传输层协议,提供高可靠性字节流传输服务,主要用于一次传输要交换大量报文的情形

    为了维护传输的可靠性,TCP增加了许多开销,如:确认、流量控制、计时器以及连接管理等

    2.TCP协议的传输特点是:

    ①端到端通信:TCP提供给应用面向连接的接口。TCP连接是端到端的,客户应用程序在一端,服务器在另一端

    ②建立可靠连接:TCP要求客户的应用程序在与服务器交换数据前,先要连接服务器,保证连接可靠建立,建立连接测试了网络的连通性,如果有故障发生,阻碍分组到达远端系统,或者服务器不接受连接,那么企图连接就会失败,客户就会得到通知

    ③可靠交付:一旦建立连接,TCP保证数据将按发送时的数据交付,没有丢失,也没有重复,如果因为故障而不能可靠交付,发送方会得到通知

    ④具有流控的传输:TCP控制数据传输的速率,防止发送方传送数据的速率快于接收方的接收速率,因此TCP可以用于从快速计算机向慢速计算机传送数据

    ⑤双工传输:在任何时候,单个TCP连接都允许同时双向传送数据,而且不会相互影响,因此客户可以向服务器发送请求,而服务器可以通过同一个连接发送应答

    ⑥流模式:TCP从发送方向接收方发送没有报文边界的字节流

    二:TCP的首部

    TCP数据被封装在一个IP数据包中

    在这里插入图片描述

    1.流式套接字依托传输控制协议(在TCP/IP协议簇中对应TCP协议),提供面向连接的,可靠的数据传输服务,该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。基于流的特点,使用流式套接字传输的数据形态是没有记录边界的有序数据流

    2.流式套接字基于可靠的数据流传输服务,这种服务的特点是面向连接、可靠。面向连接的特点决定了流式套接字的传输代价大,且只适合于一对一的数据传输;而可靠的特点意味着上层应用程序在设计开发时不需要过多的考虑数据传输过程中的丢失、乱序、重复问题

    3.TCP使用场景:①大数据量的数据传输应用②可靠性要求高的传输应用

    4.服务器:

    ①调用WSAStartup()函数加载Windows Sockets DLL,然后调用socket()函数创建一个流式套接字,返回套接字s

    ②调用bind()函数将套接字s绑定到本地的端点地址上

    ③调用listen()函数将套接字s设置为监听模式,准备接收来自各个客户的连接请求

    ④调用accept()函数等待接受客户的连接请求

    ⑤如果接收到客户的连接请求,则accept()函数返回,得到新的套接字ns(叫做连接套接字)

    ⑥调用recv()函数在套接字ns上接受来自客户的数据

    ⑦处理客户的服务器请求

    ⑧调用send()函数在套接字ns上向客户发送数据

    ⑨与客户的通信结束后,服务器进程可以调用shutdown()函数通知对方不再发送或接受数据,也可以由客户进程断开连接。断开连接后,服务器进程调用closesocket()函数关闭套接字ns。此后服务器进程继续等待客户进程的连接,回到第4步

    ⑩如果要退出服务器进程,则调用closesocket()函数关闭最初的套接字

    客户端:

    ①调用WSAStartup()函数加载Windows Sockets DLL,然后调用socket()函数创建一个流式套接字,返回套接字s

    ②调用connect()函数将套接字s连接到服务器

    ③调用send()函数向服务器发送数据,调用recv()函数接受来自服务器的数据

    ④与服务器的通信结束后,客户进程可以调用shutdown()函数通知对方不再发送或接收数据,也可以由服务器进程断开连接。断开连接后,客户进程调用closesocket()函数关闭套接字s

    5.如果服务器是循环服务器,则服务器在与一个客户建立连接后,其他客户只能等待,当一个客户服务完之后,服务器才会处理另一个客户的服务请求。在循环服务器的通信流程中步骤4~9是循环进行的

    6.如果是并发服务器,则当服务器与一个客户进行通信的过程中,可以同时接收其他客户的服务请求,并且服务器要为每一个客户创建一个单独的子进程或线程,用新创建的连接套接字与每个客户进行独立连接上的数据交互。在并发服务器的通信流程中,通过第4步返回了多个连接套接字(ns),这些连接套接字在步骤5~9与多个客户通信时是并发执行的

    7.调用getaddrinfo()函数,获得服务器的IP地址,将其存放在result指向的addinfo对象中

    更多相关内容
  • 计算机网络---网络编程套接字(一)

    千次阅读 多人点赞 2022-04-10 19:52:43
    文章目录Socket套接字概念分类UDP数据报套接字编程Java中UDP套接字编程步骤DatagramSocket APIDatagramSocket 的构造方法:DatagramSocket 的常用方法:DatagramPacket APIDatagramPacket 的构造方法DatagramPacket ...

    计算机网络—网络编程套接字之UDP数据报套接字编程


    作者介绍:

    🎓作者:偷偷敲代码的青花瓷🐱‍🚀
    👀作者的Gitee:代码仓库
    📌系列文章推荐:计算机网络 ——网络原理之初识
    ✨✨我和大家一样都是热爱编程✨,很高兴能在此和大家分享知识,希望在分享知识的同时,能和大家一起共同进步,取得好成绩🤳,今天大家进入网络编程的新章节,如果有错误❌,欢迎指正哟😋,咋们废话不多说,跟紧步伐,开始学习吧~😊

    在这里插入图片描述

    Socket套接字

    概念

    Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。

    分类

    Socket套接字主要针对传输层协议划分为如下三类:

    第一类:流套接字:使用传输层TCP协议 TCP,即Transmission Control Protocol(传输控制协议),传输层协议

    TCP的特点:
    1.有连接
    2.可靠传输
    3.面向字节流
    4.有接收缓冲区,也有发送缓冲区
    5.大小不限

    对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收


    第二类:数据报套接字:使用传输层UDP协议 , UDP,即User Datagram Protocol(用户数据报协议),传输层协议

    UDP特点:
    1.无连接
    2.不可靠传输
    3.面向数据报
    4.有接受缓冲区,无法送缓冲区
    5.大小受限:一次最多传输64k

    对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一
    次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。


    第三类:原始套接字

    原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。(简单了解即可)

    今天主要讲解Socket套接字的第二类:UDP


    UDP数据报套接字编程

    在 TCP/IP 协议的传输层除了一个 TCP 协议之外,还有一个 UDP 协议。UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP 就会表现出更大的优势。


    Java中UDP套接字编程步骤

    下面是在 Java 中使用 UDP 协议发送数据的步骤。

    1.使用 DatagramSocket() 创建一个数据包套接字。
    2.使用 DatagramPacket() 创建要发送的数据包
    3.使用 DatagramSocket 类的 send() 方法发送数据包。

    接收 UDP 数据包的步骤如下:

    1.使用 DatagramSocket 创建数据包套接字,并将其绑定到指定的端口。
    2.使用 DatagramPacket 创建字节数组来接收数据包。
    3.使用 DatagramPacket 类的 receive() 方法接收 UDP 包。

    UDP数据报套接字有两个核心类:DatagramSocketDatagramPacket


    DatagramSocket API

    DatagramSocket 是 UDP Socket,用于发送和接收UDP 数据报

    DatagramSocket 的构造方法:

    在这里插入图片描述

    DatagramSocket 的常用方法:

    在这里插入图片描述

    DatagramPacket API

    DatagramPacket 的构造方法

    在这里插入图片描述

    DatagramPacket 的常用方法

    在这里插入图片描述

    代码实例以及详细图文解析

    实例1:回显服务

    进行网络编程,第一步就需要先准备好 socket 实例,这是进行网络编程的大前提

    在这里插入图片描述

    注意:

    多个进程不能绑定同一个端口,一个进程能够绑定多个端口。

    启动服务器,分三个步骤:

    1.读取客服端发来的请求
    2.根据请求计算响应
    3.把响应写回到客户端

    在这里插入图片描述
    在这里插入图片描述

    客户端:
    在这里插入图片描述
    在这里插入图片描述

    输出结果:
    在这里插入图片描述
    在这里插入图片描述

    代码实现:

    服务器端:

    // 服务器端:
    package network;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.SocketException;
    
    // 站在服务器的角度:
    // 1. 源 IP: 服务器程序本机的 IP
    // 2. 源端口: 服务器绑定的端口 (此处手动指定了 9090)
    // 3. 目的 IP: 包含在收到的数据报中. (客户端的IP)
    // 4. 目的端口: 包含在收到的数据报中. (客户端的端口)
    // 5. 协议类型: UDP
    public class UdpEchoServer {
        // 进行网络编程, 第一步就需要先准备好 socket 实例~ 这是进行网络编程的大前提.
        private DatagramSocket socket = null;
    
        public UdpEchoServer(int port) throws SocketException {
            socket = new DatagramSocket(port);
        }
    
        // 启动服务器.
        public void start() throws IOException {
            System.out.println("启动服务器!");
            // UDP 不需要建立连接, 直接接收从客户端来的数据即可
            while (true) {
                // 1. 读取客户端发来的请求
                DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
                socket.receive(requestPacket); // 为了接受数据, 需要先准备好一个空的 DatagramPacket 对象, 由 receive 来进行填充数据
                // 把 DatagramPacket 解析成一个 String
                String request = new String(requestPacket.getData(), 0, requestPacket.getLength(), "UTF-8");
                // 2. 根据请求计算响应(由于咱们这是一个回显服务, 2 省略)
                String response = process(request);
                // 3. 把响应写回到客户端
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                        requestPacket.getSocketAddress());
                socket.send(responsePacket);
                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();
        }
    }
    

    客户端:

    package network;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    import java.net.SocketException;
    import java.util.Scanner;
    
    public class UdpEchoClient {
        private DatagramSocket socket = null;
        private String serverIP;
        private int serverPort;
    
        // 站在客户端的角度:
        // 源 IP: 本机 IP
        // 源端口: 系统分配的端口
        // 目的 IP: 服务器的 IP
        // 目的端口: 服务器的端口
        // 协议类型: UDP
        public UdpEchoClient(String ip, int port) throws SocketException {
            // 此处的 port 是服务器的端口.
            // 客户端启动的时候, 不需要给 socket 指定端口. 客户端自己的端口是系统随机分配的~~
            socket = new DatagramSocket();
            serverIP = ip;
            serverPort = port;
        }
        public void start() throws IOException {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                // 1. 先从控制台读取用户输入的字符串
                System.out.print("-> ");
                String request = scanner.next();
                // 2. 把这个用户输入的内容, 构造成一个 UDP 请求, 并发送.
                //    构造的请求里包含两部分信息:
                //    1) 数据的内容. request 字符串
                //    2) 数据要发给谁~ 服务器的 IP + 端口
                DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                        InetAddress.getByName(serverIP), serverPort);
                socket.send(requestPacket);
                // 3. 从服务器读取响应数据, 并解析
                DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
                socket.receive(responsePacket);
                String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");
                // 4. 把响应结果显示到控制台上.
                System.out.printf("req: %s, resp: %s\n", request, response);
            }
        }
        public static void main(String[] args) throws IOException {
            // 由于服务器和客户端在同一个机器上, 使用的 IP 仍然是 127.0.0.1 . 如果是在不同的机器上, 当然就需要更改这里的 IP 了
            UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
            client.start();
        }
     }
    
    

    实例2:程序翻译(英译汉 )

    请求:一些简单的英语单词 响应:英语单词对应的翻译

    这个简单的程序我们需要知道,客户端不变,把服务器代码进行调整,主要是调整 process 方法,读取请求并分析,把响应写会给客户端,这俩步骤都一样,关键的逻辑就是"根据请求处理响应"

    代码实现:

    package network;
    
    import java.io.IOException;
    import java.net.SocketException;
    import java.util.HashMap;
    
    public class UdpDictServer extends UdpEchoServer {
        private HashMap<String, String> dict = new HashMap<>();
    
        public UdpDictServer(int port) throws SocketException {
            super(port);
    
            // 简单构造几个词
            dict.put("cat", "小猫");
            dict.put("dog", "小狗");
            dict.put("fuck", "卧槽");
            dict.put("pig", "小猪");
        }
    
        @Override
        public String process(String request) {
            return dict.getOrDefault(request, "该词无法被翻译!");
        }
    
        public static void main(String[] args) throws IOException {
            UdpDictServer server = new UdpDictServer(9090);
            server.start();
        }
    }
    

    输出结果:
    在这里插入图片描述
    在这里插入图片描述

    总结

    种一颗树最好的是十年前,其次就是现在
    所以,
    让我们一起努力吧,去奔赴更高更远的山海

    如果有错误❌,欢迎指正哟😋

    🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉
    在这里插入图片描述

    展开全文
  • 书中全面深入地介绍了如何使用套接字API进行网络编程。全书不但介绍了基本编程内容,还涵盖了与套接字编程相关的高级主题,对于客户/服务器程序的各种设计方法也作了完整的探讨,最后还深入分析了流这种设备驱动机制...
  • UNIX网络编程卷1:套接字联网API(第3版)的高清pdf文件,资源下载的最低积分,高清版。
  • UNIX网络编程卷1:套接字联网API(第3版).pdf UNIX网络编程卷1:套接字联网API(第3版).pdf
  • 书中全面深入地介绍了如何使用套接字API进行网络编程。全书不但介绍了基本编程内容,还涵盖了与套接字编程相关的高级主题,对于客户/服务器程序的各种设计方法也作了完整的探讨,还深入分析了流这种设备驱动机制。 ...
  • 计算机网络---网络编程套接字(二)

    千次阅读 多人点赞 2022-04-12 14:30:33
    ✨计算机网络 — 网络编程套接字之TCP套接字编程 作者介绍: 作者:偷偷敲代码的青花瓷‍ 作者的Gitee:代码仓库 系列文章推荐: ✨1.计算机网络 ——网络原理之初识 ✨2.计算机网络—网络编程套接字(一) ✨✨我和大家...

    计算机网络 — 网络编程套接字之TCP套接字编程


    作者介绍:

    🎓作者:偷偷敲代码的青花瓷🐱‍🚀
    👀作者的Gitee:代码仓库
    📌系列文章推荐:
    ✨1.计算机网络 ——网络原理之初识
    ✨2.计算机网络—网络编程套接字(一)
    ✨✨我和大家一样都是热爱编程✨,很高兴能在此和大家分享知识,希望在分享知识的同时,能和大家一起共同进步,取得好成绩🤳,今天大家进入网络编程的新章节,如果有错误❌,欢迎指正哟😋,咋们废话不多说,跟紧步伐,开始学习吧~😊

    在这里插入图片描述


    前言:

    前面给大家介绍了计算机网络—网络编程套接字之UDP数据报套接字编程,今天和大家一起继续学习,计算机网络 — 网络编程套接字之TCP套接字编程


    TCP套接字编程基本概念

    套接字是一种网络API提供一种进程间的通信方法,使得相同主机或者不同主机上的进程能够使用socket定义的规范进行双向的数据通信。进程之间调用套接字接口实现相互通信,套接字接口利用下层的网络通信协议功能和系统调用实现实际的通信工作(这一部分对于编程者是透明的)
    简单说,通过 socket 我们可以实现进程间的通信

    TCP API中的两大核心类

    1.ServerSocket API
    2.Socket API

    ServerSocket类的常用构造方法及其方法

    ServerSocket 构造方法::

    ServerSocket(int port):创建绑定到特定端口的服务器套接字。

    ServerSocket 方法:

    accept():侦听并接受到此套接字的连接。
    getInetAddress():返回此服务器套接字的本地地址。

    Socket类的常用构造方法及其方法

    Socket 构造方法:

    1.Socket(InetAddress address, int port):
    创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
    2.Socket(String host, int port):
    创建一个流套接字并将其连接到指定主机上的指定端口号。
    3.Socket(InetAddress address, int port, InetAddress localAddr, int localPort):
    创建一个套接字并将其连接到指定远程地址上的指定远程端口。
    4.Socket(String host, int port, InetAddress localAddr, int localPort):
    创建一个套接字并将其连接到指定远程主机上的指定远程端口。

    Socket 方法:

    close()
    关闭此套接字。
    connect(SocketAddress endpoint)
    将此套接字连接到服务器。
    connect(SocketAddress endpoint, int timeout)
    将此套接字连接到服务器,并指定一个超时值。
    getInetAddress()
    返回套接字连接的地址。
    getInputStream()
    返回此套接字的输入流。
    getLocalPort()
    返回此套接字绑定到的本地端口。
    getOutputStream()
    返回此套接字的输出流。
    getPort()
    返回此套接字连接到的远程端口。

    代码实例以及详细图文解析

    实例1:TCP回显服务

    服务器:

    进行网络编程,第一步就需要先准好 socket 实例,这是网络编程的大前提

    在这里插入图片描述
    注意:

    多个进程不能绑定同一个端口,一个进程能够绑定多个端口

    启动服务器,分四个步骤

    1.建立连接
    2.读取客户端发来的请求
    3.根据请求计算响应
    4.把响应写回到客户端

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述
    客户端:

    和服务器连接成分四个步骤:

    1.从控制台读取字符串
    2.根据读取的字符串,构造请求,把请求发给服务器
    3.从服务器读取响应,并解析
    4.把结果显示到控制台上

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    输出结果:

    在这里插入图片描述

    整体代码:

    //服务器
    package network;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class TcpEchoServer {
        // listen => 英文原意 监听~~
        // 但是在 Java socket 中是体现不出来 "监听" 的含义的~~
        // 之所以这么叫, 其实是 操作系统原生的 API 里有一个操作叫做 listen
        // private ServerSocket listenSocket = null;
        private ServerSocket serverSocket = null;
        public TcpEchoServer(int port) throws IOException {
            serverSocket = new ServerSocket(port);//给服务器指定端口号
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while (true) {
                // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
                // accept 就是在 "接电话", 接电话的前提是, 有人给你打了~~, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
                // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
                // 进一步讲, serverSocket 就干了一件事, 接电话~~
                Socket clientSocket = serverSocket.accept();
                processConnection(clientSocket);
            }
        }
    
        private void processConnection(Socket clientSocket) {
            System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            // 接下来来处理请求和响应
            // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
            try (InputStream inputStream = clientSocket.getInputStream()) {
                try (OutputStream outputStream = clientSocket.getOutputStream()) {
                    // 循环的处理每个请求, 分别返回响应
                    Scanner scanner = new Scanner(inputStream);
                    while (true) {
                        // 1. 读取请求
                        if (!scanner.hasNext()) {
                            System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                            break;
                        }
                        // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
                        String request = scanner.next();
                        // 2. 根据请求, 计算响应
                        String response = process(request);
                        // 3. 把这个响应返回给客户端
                        // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
                        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 {
                // 此处要记得来个关闭操作.
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoServer server = new TcpEchoServer(9090);
            server.start();
        }
    }
    
    
    //客户端
    package network;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class TcpEchoClient {
        // 用普通的 socket 即可, 不用 ServerSocket 了
        // 此处也不用手动给客户端指定端口号, 让系统自由分配.
        private Socket socket = null;
    
        public TcpEchoClient(String serverIP, int serverPort) throws IOException {
            // 其实这里是可以给的. 但是这里给了之后, 含义是不同的. ~~
            // 这里传入的 ip 和 端口号 的含义表示的不是自己绑定, 而是表示和这个 ip 端口建立连接!!
            // 调用这个构造方法, 就会和服务器建立连接 (打电话拨号了)
            socket = new Socket(serverIP, serverPort);
        }
    
        public void start() {
            System.out.println("和服务器连接成功!");
            Scanner scanner = new Scanner(System.in);
            try (InputStream inputStream = socket.getInputStream()) {
                try (OutputStream outputStream = socket.getOutputStream()) {
                    while (true) {
                        // 要做的事情, 仍然是四个步骤
                        // 1. 从控制台读取字符串
                        System.out.print("-> ");
                        String request = scanner.next();
                        // 2. 根据读取的字符串, 构造请求, 把请求发给服务器
                        PrintWriter printWriter = new PrintWriter(outputStream);
                        printWriter.println(request);
                        printWriter.flush(); // 如果不刷新, 可能服务器无法及时看到数据.
                        // 3. 从服务器读取响应, 并解析
                        Scanner respScanner = new Scanner(inputStream);
                        String response = respScanner.next();
                        // 4. 把结果显示到控制台上.
                        System.out.printf("req: %s, resp: %s\n", request, 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();
        }
    }
    

    实例2:多线程版本TCP回显服务

    服务器:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    客户端:

    客户端不变,和上述代码一致。

    运行结果:
    在这里插入图片描述
    具体代码:

    //服务器
    package network;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class TcpThreadEchoServer {
        // 但是在 Java socket 中是体现不出来 "监听" 的含义的~~
        // 之所以这么叫, 其实是 操作系统原生的 API 里有一个操作叫做 listen
        // private ServerSocket listenSocket = null;
        private ServerSocket serverSocket = null;
    
        public TcpThreadEchoServer(int port) throws IOException {
            serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while (true) {
                // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
                // accept 就是在 "接电话", 接电话的前提是, 有人给你打了~~, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
                // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
                // 进一步讲, serverSocket 就干了一件事, 接电话~~
                Socket clientSocket = serverSocket.accept();
                // [改进方法] 在这个地方, 每次 accept 成功, 都创建一个新的线程, 由新线程负责执行这个 processConnection 方法~
                Thread t = new Thread(() -> {
                    processConnection(clientSocket);
                });
                t.start();
            }
        }
    
        private void processConnection(Socket clientSocket) {
            System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            // 接下来来处理请求和响应
            // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
            try (InputStream inputStream = clientSocket.getInputStream()) {
                try (OutputStream outputStream = clientSocket.getOutputStream()) {
                    // 循环的处理每个请求, 分别返回响应
                    Scanner scanner = new Scanner(inputStream);
                    while (true) {
                        // 1. 读取请求
                        if (!scanner.hasNext()) {
                            System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                            break;
                        }
                        // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
                        String request = scanner.next();
                        // 2. 根据请求, 计算响应
                        String response = process(request);
                        // 3. 把这个响应返回给客户端
                        // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
                        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 {
                // 此处要记得来个关闭操作.
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
            server.start();
        }
    }
    

    实例3:线程池版本

    //服务器:
    package network;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TcpThreadPoolEchoServer {
        // 但是在 Java socket 中是体现不出来 "监听" 的含义的~~
        // 之所以这么叫, 其实是 操作系统原生的 API 里有一个操作叫做 listen
        // private ServerSocket listenSocket = null;
        private ServerSocket serverSocket = null;
    
        public TcpThreadPoolEchoServer(int port) throws IOException {
            serverSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动!");
            ExecutorService pool = Executors.newCachedThreadPool();
            while (true) {
                // 由于 TCP 是有连接的, 不能一上来就读数据, 而要先建立连接. (接电话)
                // accept 就是在 "接电话", 接电话的前提是, 有人给你打了~~, 如果当前没有客户端尝试建立连接, 此处的 accept 就会阻塞.
                // accept 返回了 一个 socket 对象, 称为 clientSocket. 后续和客户端之间的沟通, 都是通过 clientSocket 来完成的.
                // 进一步讲, serverSocket 就干了一件事, 接电话~~
                Socket clientSocket = serverSocket.accept();
                // [改进方法] 在这个地方, 每次 accept 成功, 都创建一个新的线程, 由新线程负责执行这个 processConnection 方法~
                // 通过线程池来实现
                pool.submit(new Runnable() {
                    @Override
                    public void run() {
                        processConnection(clientSocket);
                    }
                });
            }
        }
    
        private void processConnection(Socket clientSocket) {
            System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            // 接下来来处理请求和响应
            // 这里的针对 TCP socket 的读写就和文件读写是一模一样的!!
            try (InputStream inputStream = clientSocket.getInputStream()) {
                try (OutputStream outputStream = clientSocket.getOutputStream()) {
                    // 循环的处理每个请求, 分别返回响应
                    Scanner scanner = new Scanner(inputStream);
                    while (true) {
                        // 1. 读取请求
                        if (!scanner.hasNext()) {
                            System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                            break;
                        }
                        // 此处用 Scanner 更方便. 如果不用 Scanner 就用原生的 InputStream 的 read 也是可以的
                        String request = scanner.next();
                        // 2. 根据请求, 计算响应
                        String response = process(request);
                        // 3. 把这个响应返回给客户端
                        // 为了方便起见, 可以使用 PrintWriter 把 OutputStream 包裹一下
                        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 {
                // 此处要记得来个关闭操作.
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
            server.start();
        }
    }
    

    总结

    “种一颗树最好的是十年前,其次就是现在”
    所以,
    “让我们一起努力吧,去奔赴更高更远的山海”

    如果有错误❌,欢迎指正哟😋

    🎉如果觉得收获满满,可以动动小手,点点赞👍,支持一下哟🎉
    在这里插入图片描述

    展开全文
  • UNIX网络编程卷1:套接字联网API(第3版).pdf
  • 第一章 理解网络编程套接字 1.1 网络编程套接字概要 1.2 基于Linux的文件操作 1.2.1 底层文件访问和文件描述符 1.2.2 打开文件 1.2.3 关闭文件 1.2.4 将数据写入文件 1.2.5 读取文件中的数据 第二章 ...

    目录

    第一章 理解网络编程和套接字

    1.1 网络编程和套接字概要

    1.2 基于Linux的文件操作

    1.2.1 底层文件访问和文件描述符

    1.2.2 打开文件

    1.2.3 关闭文件

    1.2.4 将数据写入文件

    1.2.5 读取文件中的数据

    第二章 套接字类型与协议设置

    2.1 套接字协议及其数据传输特性

    2.1.1 关于协议

    2.1.2 创建套接字

    第三章 地址族与数据序列

    3.1 分配给套接字的IP地址与端口号

    3.1.1 网络地址

    3.1.2 用于区分套接字的端口号

    3.2 地址信息的表示

    3.2.1 表示IPv4地址的结构体

    3.3 网络字节序与地址变换

    3.3.1 字节序与网络字节序

    3.3.2 字节序转换

    3.4 网络地址的初始化与分配

    3.4.1 将字符串信息转换为网络字节序的整数型

    3.4.2 网络地址初始化

    3.4.3 INADDR_ANY

    3.4.4 向套接字分配网络地址

    第四章 基于TCP的服务器端

    4.1 理解TCP和UDP

    4.1.1 TCP/IP协议栈

    4.2 实现基于TCP的服务器端/客户端

    4.2.1 TCP服务器端的默认函数调用顺序

    4.2.2 服务端进入等待连接请求阶段

    4.2.3 服务端受理客户端连接请求

    4.2.4 TCP客户端的默认函数调用顺序

    4.2.5 基于TCP的服务器端/客户端函数调用关系

    4.3 实现迭代服务器端/客户端

    4.3.1 实现迭代服务器端

    第五章 TCP原理

    5.1 TCP套接字中的I/O缓冲

    5.2 TCP内部工作原理1:与对方套接字的连接

    5.3 TCP内部工作原理2:与对方主机的数据交换

    5.4 TCP的内部工作原理3:断开与套接字的连接


    第一章 理解网络编程和套接字

    1.1 网络编程和套接字概要

       网络编程就是编写程序使两台连网的计算机相互交换数据。首先需要的是物理连接,目前大部分计算机都连接到庞大的互联网中;在此基础上,只需考虑如何编写数据传输软件,而操作系统为我们提供了名为"套接字"的部件,套接字是数据网络传输用的软件设备。网络编程又称为套接字编程。

       服务器端是能够受理连接请求的程序,服务器端创建的套接字称为服务器端套接字或监听套接字;客户端是用于请求连接的,客户端创建的套接字称为客户端套接字。

    1.2 基于Linux的文件操作

       在Linux系统中,套接字socket也被认为是文件的一种,因此在网络传输过程中可以使用文件I/O的相关函数;而Windows系统于Linux不同,要区分socket和文件,因此两种系统中的编程方式也不相同。之后主要学习的是Linux的编程方式。

    1.2.1 底层文件访问和文件描述符

       底层指的是与标准无关的操作系统独立提供的。文件描述符是系统分配给文件或套接字的整数,这个整数将成为程序员与操作系统之间良好沟通的渠道,是为了方便称呼操作系统创建的文件或套接字而赋予的数。有一些文件描述符是固定的,比如C语言中学习的标准输入输出及标准错误,即描述符从3开始由小到大的顺序编号,因为0、1、2是分配给标准I/O的描述符。

    文件描述符对象
    0标准输入:Standard Input
    1标准输出:Standard Output
    2标准错误:Standard Error

       文件和套接字一般经过创建过程才会被分配文件描述符。

    1.2.2 打开文件

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <fcntl.h>

    int open(const char *path, int flag);//成功时返回文件描述符,失败时返回-1

    • path:文件名的字符串地址
    • flag:文件打开模式信息

       文件打开模式flag可能的常量值和含义:

    打开模式含义
    O_CREAT必要时创建文件
    O_TRUNG删除全部现有数据
    O_APPEND维持现有数据,保存到其后面
    O_RDONLY只读打开
    O_WRONLY只写打开
    O_RDWD读写打开

    1.2.3 关闭文件

    #include <unistd.h>

    int close(int fd);//成功时返回0,失败时返回-1

    • fd:需关闭的文件或套接字的文件描述符

       此函数不仅可以关闭文件,也可以关闭套接字。

    1.2.4 将数据写入文件

    #include <unistd.h>

    ssize_t write(int fd,const void *buf,size_t nbytes);//成功时返回写入的字节数,失败时返回-1

    • ssize_t:signed int   size_t:unsigned int   使用typedef声明的两个数据类型
    • fd:显示数据传输对象的文件描述符
    • buf:保存要传输数据的缓存地址值
    • nbytes:要传输数据的字节数

       用于向文件输出(传输数据);通过套接字向其他计算机传递数据。

    1.2.5 读取文件中的数据

    #include <unistd.h>

    ssize_t read(int fd, void *buf,size_t nbytes);

    • fd:显示数据传输对象的文件描述符
    • buf:保存要接收数据的缓存地址值
    • nbytes:要接收数据的最大字节数

    第二章 套接字类型与协议设置

    2.1 套接字协议及其数据传输特性

    2.1.1 关于协议

       协议是对话中使用的通信规则,在计算机领域内理解为计算机间对话必备通信规则

    2.1.2 创建套接字

    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);//成功时返回文件描述符,失败时返回-1

    • domain:套接字中使用的协议族信息
    • type:套接字数据传输类型信息
    • protocol:计算机间通信中使用的协议信息

       头文件sys/socket.h中声明的协议族

    名称协议族
    PF_INETIPv4互联网协议族
    PF_INET6IPv6互联网协议族
    PF_LOCAL本地通信的UNIX协议族
    PF_PACKET底层套接字的协议族
    PF_IPXIPX Novell协议族

       套接字类型Type指的是套接字的数据传输方式,因为决定了协议族并不能同时决定数据传输方式,一个协议族内存在多种数据传输方式:

    • SOCK_STREAM面向连接的套接字。
      • 特征:
        • 传输过程中数据不会消失
        • 按序传输数据
        • 传输的数据不存在数据边界
      • 传输的数据不存在数据边界,指的是收发数据的套接字内部有缓冲(字节数组),因此收到数据并不意味着马上调用read函数,只要不超过缓冲容量,则有可能在数据填充满缓冲后1次read函数调用读取全部,也有可能分成多次read函数调用进行读取。在面向连接的套接字中,read函数和write函数的调用次数并无太大意义
      • 面向连接的套接字连接必须一一对应,只能与另外一个同样特性的套接字连接
      • 可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字
    • SOCK_DGRAM面向消息的套接字。
      • 特征:
        • 强调快速传输而非传输顺序
        • 传输的数据可能丢失也可能损毁
        • 传输的数据有数据边界
        • 限制每次传输的数据大小
      • 接收数据的次数应和传输次数相同
      • 不可靠的、不按序传递的、以数据的高速传输为目的的套接字

       第三个参数决定最终采用的协议。除非遇到同一协议族中存在多个数据传输方式相同的协议,大部分情况下可以向第三个参数传递0。

       常用调用方式:

            int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);

            0指的是满足PF_INET和SOCK_STREAM条件的协议IPPROTO_TCP

            int udp_socket = socket(PF_INET, SOCK_DGRAM, 0);

            0指的是满足PF_INET和SOCK_DGRAM条件的协议IPPROTO_UDP

    第三章 地址族与数据序列

    3.1 分配给套接字的IP地址与端口号

       IP(Internet Protocol),是为收发网络数据而分配给计算机的值。端口号是为区分程序中窗口的套接字而分配给套接字的符号。

    3.1.1 网络地址

       IP地址分为两类:

            IPv4:4字节地址族

            IPv6:16字节地址族

       IPv4标准的4字节IP地址分为网络地址主机(指计算机)地址,分为A、B、C、D、E类型。数据传输时先根据网络地址找到计算机所属的局域网,再根据主机地址找到该局域网下的那个计算机。向相应网络传输数据实际上是向构成网络的路由器或交换机传递数据,由接收数据的路由器根据数据中的主机地址向目标主机传递数据。

       通过IP地址的第一个字节判断网络地址占用的字节数,即判断所属类型:

    • A类地址的首字节范围:0~127
    • B类地址的首字节范围:128~191
    • C类地址的首字节范围:192~223

    3.1.2 用于区分套接字的端口号

       IP用于区分计算机,只要有IP地址就能向目标主机传输数据,但仅凭这些无法传输给最终的应用程序。计算机中一般配有NIC(网络接口卡)数据传输设备,通过NIC向计算机内部传输数据时会用到IP。操作系统负责把传递到内部的数据适当分配给套接字,这是就要利用端口号。即通过NIC接收的数据内有端口号,操作系统正是参考此端口号把数据传输给相应端口的套接字。

       端口号就是在同一操作系统内为区分不同套接字而设置的,不能将1个端口号分配给不同的套接字。端口号由16位构成,可分配的端口号范围是0-65535。注意:虽然端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许重复

    3.2 地址信息的表示

    3.2.1 表示IPv4地址的结构体

    struct sockaddr_in{

            sa_family_t  sin_family;   //地址族

            uint16_t  sin_port;   //16位TCP/UDP端口号

            struct in_addr  sin_addr;   //32位ip地址

            char  sin_zero[8];   //不使用

    }

    struct in_addr{

            In_addr_t  s_addr;   //32位IPv4地址

    }

       这些数据类型参考:

    • 成员sin_family:每种协议族适用的地址族均不同。
    地址族含义
    AF_INETIPv4网络协议中使用的地址族
    AF_INET6IPv6网络协议中使用的地址族
    AF_LOCAL本地通讯中采用的UNIX协议的地址族
    • 成员sin_port:保存16位端口号,重点在于以网络字节序保存
    • 成员sin_addr:保存32位IP地址信息,也以网络字节序保存
    • 成员sin_zero:无特殊含义,只是为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员,必须填充为0

    struct sockaddr{

            sa_family_t  sin_family;   //地址族

            char  sa_data[14];   //地址信息

    }

    • sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应应填充0。

    3.3 网络字节序与地址变换

    3.3.1 字节序与网络字节序

       CPU向内存保存数据的方式有两种:

    • 大端序:高位字节存放在低位地址

    • 小端序:高位字节存放在高位地址

       代表CPU数据保存方式的主机字节序在不同CPU中也各不相同(目前主流的Intel系列CPU以小端序方式保存数据),因此如果两台计算机的CPU数据保存方式不同,会导致传送的数据和接收后解析的数据不相同。

       正因如此,在通过网络传输数据时约定统一方式,这种约定称为网络字节序,其实非常简单,就是统一为大端序,先把数据数组转化成大端序格式再进行网络传输。

    3.3.2 字节序转换

       转换字节序的函数:

    unsigned short htons(unsigned short);

    unsigned short ntohs(unsigned short);

    unsighed long htonl(unsigned long);

    unsigned long ntohl(unsigned long);

    • h代表主机host字节序
    • n代表网络network字节序
    • s代表short类型,用于端口号转换
    • l代表long类型(Linux中long类型占用4个字节),用于IP地址转换

       注意:数据的收发过程中有自动转换机制,因此除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题。

    3.4 网络地址的初始化与分配

    3.4.1 将字符串信息转换为网络字节序的整数型

       sockaddr_in中保存地址信息的成员为32位整数型,因此需要把点分十进制表示法形式的IP地址转换成32位整数型数据。这个函数在转换类型的同时进行网络字节序转换

    #include <arpa/inet.h>

    in_addr_t inet_addr(const char *string);//成功时返回32位大端序整数型值,失败时返回INADDR_NONE

    int inet_aton(const char *string, struct in_addr *addr);//成功时返回1,失败时返回0

    • string:含有需转换的IP地址信息的字符串地址值
    • addr:将保存转换结果的in_addr结构体变量的地址值

       调用inet_addr函数,需将转换后的IP地址信息带入sockaddr_in结构体中声明的in_addr结构体变量。而inet_aton函数不需要此过程,原因在于,若传递in_addr结构体变量值,函数会自动把结果传入该结构体变量。

    #include <arpa/inet.h>

    char* inet_ntoa(struct in_addr adr);//成功时返回转换的字符串地址值,失败时返回-1

       此函数可以把网络字节序整数型IP地址转换为字符串形式。这个函数在内部申请了内存并保存了字符串,因此调用完该函数后,应立即将字符串信息复制到其他内存空间,防止再次调用该函数产生的覆盖问题。

    3.4.2 网络地址初始化

       常见的网络地址信息初始化方法:

    3.4.3 INADDR_ANY

       利用常数INADDR_ANY分配服务器的IP地址,采用这种方法,可以以自动获取运行服务器端的计算机IP地址

    3.4.4 向套接字分配网络地址

    #include <sys/socket.h>

    int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);//成功时返回0,失败时返回-1

    • sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符
    • myaddr:存有服务端地址信息的结构体变量地址值
    • addrlen:第二个结构体变量的长度

       常见的套接字初始化过程:

    第四章 基于TCP的服务器端

    4.1 理解TCP和UDP

       根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字。

       TCP套接字是面向连接的,又称基于流的套接字。TCP是Transmission Control Protocol (传输控制协议),意为对数据传输过程的控制。

    4.1.1 TCP/IP协议栈

       IP本身是面向消息的、不可靠的协议,用于路径的选择,如果传输中发送路径错误,则选择其他路径,如果发生数据丢失或错误,则无法解决。TCP和UDP层以IP层提供的路径信息为基础完成实际的数据传输。其中TCP在数据交换过程中可以确认对方已收到数据,并重传丢失的数据,因此TCP协议确认后向不可靠的IP协议赋予可靠性

       根据程序特点决定服务器端和客户端之间的数据传输规则(规定),这就是应用层协议。

    4.2 实现基于TCP的服务器端/客户端

    4.2.1 TCP服务器端的默认函数调用顺序

    4.2.2 服务端进入等待连接请求阶段

       调用bind函数给套接字分配了地址,接下来通过调用listen函数进入等待连接请求状态。只有服务端调用了listen函数,客户端才能进入可发生连接请求的状态(此时客户端才能调用connect函数)。

    #include <sys/socket.h>

    int listen(int sock,int backlog);//成功时返回0,失败时返回-1

    • sock:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)
    • backlog:连接请求等待队列的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列

       客户端请求连接时,服务端受理连接前一直使请求处于等待状态。

    4.2.3 服务端受理客户端连接请求

       调用listen函数后,若有新的连接请求,则应按序受理。受理请求意味着进入可接收数据的状态。此时除了listen创建出的套接字外,还需要另外一个套接字,来连接到发起请求的客户端,接受数据并写回数据,但是这个套接字不需要自己手动创建,下面这个函数会自动创建套接字。

    #include <sys/socket.h>

    int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);//成功时返回创建的套接字文件描述符,失败时返回-1

    • sock:服务器套接字的文件描述符
    • addr:保存发起请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息
    • addrlen:第二个参数addr结构体的长度,但是存有长度的变量地址。函数调用完后,该变量即被填入客户端地址长度

       accept函数内部将产生用于数据I/O的套接字,并返回其文件描述符,需要强调的是套接字是自动创建的,并自动与发起连接请求的客户端建立连接

    4.2.4 TCP客户端的默认函数调用顺序

       创建客户端套接字后向服务器端发起请求连接。

    #include <sys/socket.h>

    int connect(int sock, struct sockaddr * servaddr, socklen_t addrlen);//成功时返回0,失败时返回-1

    • sock:客户端套接字文件描述符
    • servaddr:保存目标服务器端地址信息的变量地址值
    • addrlen:以字节为单位传递已传递给第二个结构体参数servaddr的地址变量长度

       客户端调用connect函数后,发生以下情况之一才会返回(完成函数调用):

    • 服务器端接收连接请求
      • 注意:接收连接请求并不意味着服务器端调用accept函数,而是服务器端把连接请求信息记录到等待队列
    • 发生断网等异常情况而中断连接请求

       客户端套接字何时、何地、如何分配地址信息?

            何时:调用connect函数时

            何地:操作系统内核

            如何:IP用计算机(主机)的IP,端口随机

    4.2.5 基于TCP的服务器端/客户端函数调用关系

        注意:

    • 客户端只能等到服务器调用listen函数后才能调用connect函数
    • 客户端调用connect函数前,服务器端有可能率先调用accept函数(此时服务器端在调用accept函数时进入阻塞状态,直到客户端调用connect函数为止)

    4.3 实现迭代服务器端/客户端

    4.3.1 实现迭代服务器端

       服务器端在同一时刻只能服务于一个客户端,即只能连接一个客户端,因此每一次都要重新调用accept函数。

    • 但是可能存在一个问题:由于TCP是面向连接的套接字,不存在数据边界,客户端多次调用write函数传递的字符串有可能一次性传递到服务器端,此时客户端可能同时把这些字符串接收到,导致不满足我们的期望;也有可能客户端还没有收到全部数据包就调用了read函数。
    • 解决方法:应用层协议的定义,收发数据时要根据期望要求定义好规则(协议)来表示数据的边界。

    第五章 TCP原理

    5.1 TCP套接字中的I/O缓冲

       write函数调用后并非立即传输数据,read函数调用后也并非马上接收数据,而是write函数调用瞬间,数据将移至输出缓冲,read函数调用瞬间,从输入缓冲读取数据。

    • I/O缓冲在每个TCP套接字中单独存在
    • I/O缓冲中在创建套接字时自动生成
    • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
    • 关闭套接字将丢失输入缓冲中的数据

       TCP中有滑动窗口协议,会控制数据流(流量控制),不会发生超过输入缓冲大小的数据传输。因此TCP中不会因为缓冲溢出而丢失数据。

       TCP套接字从创建到消失过程:

    • 与对方套接字建立连接
    • 与对方套接字进行数据交换
    • 断开与对方套接字的连接

    5.2 TCP内部工作原理1:与对方套接字的连接

       TCP在实际通信过程中会经过三次对话过程,三次握手

       套接字是以双全工方式工作的,可以双向传递数据

       TCP连接收发数据前都会向数据包分配序号,并向对方通报此序号,防止数据丢失。

    5.3 TCP内部工作原理2:与对方主机的数据交换

       每一次ACK号的增量为传输的数据字节数。

    5.4 TCP的内部工作原理3:断开与套接字的连接

       断开连接时也需要双方协商,四次握手

     

    PS:参考书籍《TCP/IP网络编程》 尹圣雨著 归纳整理

    展开全文
  • Linux网络编程套接字

    千次阅读 2022-04-02 16:53:18
    文章目录预备知识理解源IP地址和目的IP地址理解 "端口号" 和 "进程ID"理解源端口号和目的端口号认识TCP协议认识UDP协议网络字节序点分...我们之前学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号
  • java套接字编程 本教程是Java套接字编程的简介,从一个简单的客户机-服务器示例开始,该示例演示了Java I / O的基本功能。 将向您介绍原始的java.io软件包和NIO,即Java 1.4中引入的非阻塞I / O( java.nio )API。 ...
  • APUE UINX环境高级编程 网络编程 socket套接字 字节序 字节序转换函数 字节操纵函数 地址转换函数 地址结构 基本TCP套接字编程 概要 socket函数 bind函数 listen函数 accept函数 connect函数 通信函数 read 和 write...
  • 我们来解释一下:从system call(系统调用结构)开始往下,都是属于OS和硬件的范畴,我们一般的程序员所进行的开发,通常都是用户层。(这个我们后面还会具体说到) 其中,TCP/IP协议属于传输层,准确点来说,其...
  • 网络编程——原始套接字实现原理

    千次阅读 多人点赞 2018-10-26 22:25:10
     1.3、网络层原始套接字 2、原始套接字的实现 2.1 原始套接字报文收发流程 2.2链路层原始套接字的实现  2.2.1 套接字创建 2.2.2 报文接收 2.2.3 报文发送 2.2.4 其它  2.3 网络层原始套接字的实现 2.3.1...
  • 书中全面深入地介绍了如何使用套接字API进行网络编程。全书不但介绍了基本编程内容,还涵盖了与套接字编程相关的高级主题,对于客户/服务器程序的各种设计方法也作了完整的探讨,最后还深入分析了流这种设备驱动机制...
  • C++的TCP套接字编程的基本步骤

    千次阅读 2022-03-31 10:32:15
    5用返回新的套接字和客户端进行通信,即发送或接收数据(使用函数send或recv),通信结束就关闭这个新创建的套接字使用函数closesocket)。 6监听套接字继续处于监听状态,等待其他客户端的连接请求。 7如果要退出...
  • 在网络编程中经常跟socket这个单词打交道,译为 “插座、插口”,而我们常称为”套接字“ 联想现实生活中的插座、插口,如电脑的USB接口,其作用就是作为两个对象之间的数据收发或能量传递;同理,在网络编程中,...
  • 1.原始套接字是允许访问底层传输协议的一种套接字类型,提供了普通套接字所不具备的功能,能够对网络数据包进行某种程度的控制操作 因此原始套接字通常用于开发简单网络性能监视程序以及网络探测、网络攻击 2.从用户...
  • 文章目录零、学习目标一、Socket概述(一)两种传输模式(二)基于Socket网络编程三、...掌握基于TCP/IP协议的套接字网络编程 一、Socket概述 Socket(套接字)是一种通信机制,可以实现单机或跨网络进行通信,其创
  • JAVA网络编程资料(1)-Socket套接字—Java套接字编程.chm
  • Python网络编程——使用套接字SocketSocket简介服务端客户端运行结果 Socket简介 Socket又称"套接字",应用程序一般通过"套接字"向网络发出请求,或应答网络请求,使两台主机或者一台计算机上的进程间可以进行通信。...
  • 网络编程】---C++实现原始套接字捕获数据包

    千次阅读 多人点赞 2019-12-18 18:09:21
    网络编程】—C++实现原始套接字捕获数据包 引言:通过创建原始套接字,获取本地可用的Ip地址,选择捕获数据包使用的网卡,绑定本地地址后,设置网卡为混杂模式,实现循环接收并显示数据来实现捕获数据包。 ...
  • 计算机网络自顶向下方法套接字编程作业

    千次阅读 多人点赞 2018-11-30 22:34:02
    本博客是针对,《计算机网络自顶向下方法》一书第二章后面套接字编程作业, 所有代码均已上传至我的github:https://github.com/inspurer/ComputerNetwork 所有代码均本人亲自编写,有问题欢迎评论交流; 如需转载请...
  • 前文分享了Wireshark抓包原理知识,并结合NetworkMiner工具抓取了图像资源和用户名密码,本文将讲解Python网络攻防相关基础知识,包括正则表达式、Web编程套接字通信。本文参考了爱春秋ADO老师的课程内容,这里也...
  • 计算机网络实验:UDP套接字编程

    千次阅读 2020-05-11 18:28:49
    计算机网络实验:UDP套接字编程一、安装实验环境二、编写程序并调试程序三、实验代码 一、安装实验环境 这次实验使用的是C/C++来编写套接字程序,可以安装DEVC++、VC6.0、CodeBlocks等,这里我们选择CodeBlocks。...
  • 第二部分 基本套接字编程 第3章 套接字编程简介 第4章 基本TCP套接字编程 第5章 TCP客户/服务器程序示例 第6章 I/O复用:select和poll函数 第7章 套接字选项 第8章 基本UDP套接字编程 第9章 基本SCTP...
  • 网络套接字编程(socket 详解)

    千次阅读 多人点赞 2019-06-09 22:54:39
    既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接...
  • Linux下的TCP套接字编程

    千次阅读 2021-12-23 19:22:01
    创建socket套接字 //函数原型 #include<sys/types.h> #include <sys/socket.h> int socket(int family, int type, int protocol); /*返回值 成功:非负描述字,即非负整数值,称为套接字。同文件描述符...
  • Linux网络编程——Unix本地套接字

    万次阅读 多人点赞 2017-10-26 12:13:18
    概述 今天,给大家讲解网络编程中的一个内容——Unix 本地套接字。  发现很多人不知道或者不太了解 Unix 本地套接字这个概念,这也难怪,socket API 原本就是为多台主机之间网络通信设计的,并且这种网络 socket ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 191,978
精华内容 76,791
热门标签
关键字:

在使用套接字进行网络编程时