一文带你了解socket网络编程以及详解过程和原理_socket编程-程序员宅基地

技术标签: java  网络  协议  通信  服务器  

在这里插入图片描述

创作不易,期望亲友们给个免费的在这里插入图片描述就行


一、什么是socket

Socket(套接字)是计算机网络编程中的一种抽象概念,它提供了在网络上进行通信的接口。通过使用 Socket,可以在不同计算机之间建立连接,并进行数据的传输和交换。

Socket 可以用于实现各种网络应用,例如:
1. 客户端-服务器通信:通过 Socket,客户端可以与服务器建立连接并发送请求,服务器接收请求并返回响应。
2. 网络聊天和即时通讯:通过 Socket,可以在多个用户之间实现实时的文字、音频或视频通信。
3. 文件传输:可以使用 Socket 在不同计算机之间传输文件,如上传和下载文件。
4. 远程控制:可通过 Socket 在远程计算机上执行指令或操作。
5. P2P(点对点)通信:Socket 提供了直接的点对点通信接口,允许两个或多个计算机直接进行数据交换。

具体使用 Socket 进行网络编程时,需要考虑协议、端口、IP 地址、数据格式等因素,以确保通信的正确性和有效性。不同编程语言和平台都有相应的 Socket API 和库,可以根据自己的需求选择合适的工具和技术。

二、TCP/IP

要理解socket必须的得理解tcp/ip,它们之间好比送信的线路和驿站的作用,比如要建议送信驿站,必须得了解送信的各个细节。
在这里插入图片描述
这里说一下tcp/ip的如何工作的,以网站简单介绍一下:

应用程序阶段:

  • 打开浏览器,在浏览器地址栏中输入网址,按下“Enter”键。此时网址信息与相关数据会被浏览器包成一个数据,并向下传给TCP/IP的应用层。

  • 应用层:由应用层提供HTTP通信协议,将来自浏览器的数据封装起来,并给予一个应用层报头,再向传输层丢去。

  • 传输层:由于HTTP为可靠连接,因此将该数据丢入TCP封装内,并给予一个TCP封装的报头,向网络层丢去。

  • 网络层:将TCP数据封装到IP数据包内,再给予一个IP包头,向网络接口层丢去

  • 网络接口层:若使用以太网络,IP会依据CSMA/CD标准,封装到MAC数据帧中,并给予MAC帧头,再转成比特流后,利用传输介质发送到远程主机上。

等到该网站收到数据包后,再以相反方向拆解开来,然后交给对应层次进行分析,最后就让该网站的WWW服务器软件获知你所想要的数据,该服务器软件再根据你的要求取得正确资料,再依据上次流程,传递到你的手上


其中还需要了解的是tcp连接的三次握手:
在这里插入图片描述

TCP 三次握手(Three-Way Handshake)是建立 TCP 连接时的一种过程,用于确保双方能够正常通信。下面是 TCP 三次握手的步骤:

1. 第一次握手(SYN-Sent):

  • 客户端向服务器发送一个带有 SYN(同步序列编号)标志的数据包,表示客户端请求建立连接。
  • 客户端选择一个初始序列号(ISN)并将其放入 SYN 数据包中,用于后续的数据传输。

2. 第二次握手(SYN-Received):

  • 服务器收到客户端的 SYN 数据包后,确认收到,并发送一个带有 SYN/ACK(同步/确认)标志的数据包给客户端。
  • 服务器也选择一个初始序列号并将其放入 SYN/ACK 数据包中。

3. 第三次握手(ESTABLISHED):

  • 客户端收到服务器的 SYN/ACK 数据包后,确认收到,并向服务器发送一个带有 ACK(确认)标志的数据包。
  • 客户端确认标识自己已经收到了服务器发来的确认,并且服务器也确认了客户端的请求连接。

经过三次握手后,TCP 连接就建立起来了,双方可以开始进行数据传输。这个过程能够保证两端的初始序列号和连接参数都得到确认,同时确认双方的接收能力和发送能力正常。如果有一方没有收到另一方的确认,会触发超时重传机制,保证连接的可靠性。

三、socket原理

大致的了解了应用程序和tcpip协议的大致关系,我们只是知道socket编程是在tcp/IP上的网络编程,但是socket在上述的模型的什么位置呢。
在这里插入图片描述
我们可以发现socket就在应用程序的传输层和应用层之间,设计了一个socket抽象层,传输层的底一层的服务提供给socket抽象层,socket抽象层再提供给应用层,问题又来了,应用层和socket抽象层之间和传输层,网络层之间如何通讯的呢,了解这个之前,我们还是回到原点

要想理解socket编程怎么通过socket关键词实现服务器和客户端通讯,必须得实现的了解tcp/ip是怎么通讯的,在这个的基础上在去理解socket的握手通讯

在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接,大致如下

第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认

第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

三次握手如下图:
在这里插入图片描述

在这里插入图片描述

四、代码说明

客户端

public class webClient {
    
    public static void main(String[] args) {
    
        Scanner scanner=new Scanner(System.in);
        String messageA=""; //储存A发送的消息
        try {
    
            // 创建 Socket,连接到服务器的 IP 地址和端口
            Socket socket = new Socket("127.0.0.1", 8888);

            // 获取输入流和输出流
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
           while (true){
      //一直保持发送
               System.out.print("输入消息:"+'\t');
               // 输入消息
               messageA=scanner.nextLine();
               // 发送消息给服务器
               outputStream.write(messageA.getBytes());
               // 接收服务器的响应
               byte[] buffer = new byte[1024];
               int length = inputStream.read(buffer);
               String response = new String(buffer, 0, length);
               System.out.println("B: " + response);
               messageA="";
           }
//            // 关闭连接
//            outputStream.close();
//            inputStream.close();
//            socket.close();

        } catch (IOException e) {
    
            System.out.println(e.getMessage());
        }
    }
}

服务端

public class webServer  {
    
    public static void main(String[] args) {
    
        Scanner scanner=new Scanner(System.in);
        String messageB=""; //储存B发送的消息
        try {
    
            // 创建 ServerSocket,监听指定的端口
            ServerSocket serverSocket = new ServerSocket(8888);

            System.out.println("服务启动,等待客户端连接... ");

            // 等待客户端连接
            Socket socket = serverSocket.accept();

            // 获取输入流和输出流
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

           while (true){
    
               // 接收客户端发送的数据
               byte[] buffer = new byte[1024];
               int length = inputStream.read(buffer);
               String message = new String(buffer, 0, length);
               System.out.println("A: " + message);

               System.out.print("输入消息:"+'\t');
               messageB=scanner.nextLine();
               // 发送响应给客户端
               outputStream.write(messageB.getBytes());
           }
//            // 关闭连接
//            outputStream.close();
//            inputStream.close();
//            socket.close();
//            serverSocket.close();

        } catch (IOException e) {
    
            e.printStackTrace();
        }
    }

}


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

五、API函数

1.创建套接字──socket()

SOCKET PASCAL FAR socket(int af, int type, int protocol)  

该调用要接收三个参数:af、type、protocol。参数af指定通信发生的区域:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。参数type 描述要建立的套接字的类型。这里分三种:

(1)一是TCP流式套接字(SOCK_STREAM)提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。

(2)二是数据报式套接字(SOCK_DGRAM)提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。

(3)三是原始式套接字(SOCK_RAW)该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。

参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此,socket()系统调用实际上指定了相关五元组中的“协议”这一元。


2.指定本地地址──bind()
当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:

int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);  

参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数name 是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。namelen表明了name的长度。如果没有错误发生,bind()返回0。否则返回SOCKET_ERROR。


3.建立套接字连接──connect()与accept()

这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。accept()用于使服务器等待来自某客户进程的实际连接。

connect()的调用格式如下:

int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen); 

参数s是欲建立连接的本地套接字描述符。参数name指出说明对方套接字地址结构的指针。对方套接字地址长度由namelen说明。

如果没有错误发生,connect()返回0。否则返回值SOCKET_ERROR。在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立。

由于地址族总被包含在套接字地址结构的前两个字节中,并通过socket()调用与某个协议族相关。因此bind()和connect()无须协议作为参数。

accept()的调用格式如下:

SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);  

参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen 为客户方套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。

accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。

四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,无论是否面向连接,均要调用bind(),若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。


4.监听连接──listen()
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:

int PASCAL FAR listen(SOCKET s, int backlog); 

参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。

listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。

调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。


5.数据传输──send()与recv()
当一个连接建立以后,就可以传输数据了。常用的系统调用有send()和recv()。

send()调用用于s指定的已连接的数据报或流套接字上发送输出数据,格式如下:

int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);  

参数s为已连接的本地套接字描述符。buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否发送带外数据等。如果没有错误发生,send()返回总共发送的字节数。否则它返回SOCKET_ERROR。

recv()调用用于s指定的已连接的数据报或流套接字上接收输入数据,格式如下:

int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);  

参数s 为已连接的套接字描述符。buf指向接收输入数据缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否接收带外数据等。如果没有错误发生,recv()返回总共接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。


6.输入/输出多路复用──select()
select()调用用来检测一个或多个套接字的状态。对每一个套接字来说,这个调用可以请求读、写或错误状态方面的信息。请求给定状态的套接字集合由一个fd_set结构指示。在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集,同时, select()调用返回满足条件的套接字的数目,其调用格式如下:

int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);  

参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略。

参数readfds指向要做读检测的套接字描述符集合的指针,调用者希望从中读取数据。参数writefds 指向要做写检测的套接字描述符集合的指针。exceptfds指向要检测是否出错的套接字描述符集合的指针。timeout指向select()函数等待的最大时间,如果设为NULL则为阻塞操作。select()返回包含在fd_set结构中已准备好的套接字描述符的总数目,或者是发生错误则返回SOCKET_ERROR。


7.关闭套接字──closesocket()

closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式如下:

BOOL PASCAL FAR closesocket(SOCKET s);  

参数s待关闭的套接字描述符。如果没有错误发生,closesocket()返回0。否则返回值SOCKET_ERROR。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_68987535/article/details/131546090

智能推荐

offSetWidth与Width的区别_html offsetwidth width区别-程序员宅基地

文章浏览阅读1.1k次。转载自:offsetwidth与width的区别,js获取html元素的实际高度1.offsetWidth属性可以返回对象的padding+border+width属性值之和,style.width返回值就是定义的width属性值。2.offsetWidth属性仅是可读属性,而style.width是可读写的。3.offsetWidth属性返回值是整数,而style.width的返回值是字符串,并..._html offsetwidth width区别

Android 获取assets下文件的绝对路径_获得assets绝对路径-程序员宅基地

文章浏览阅读1.1w次。https://blog.csdn.net/liaowenfeng/article/details/17717289/_获得assets绝对路径

管家婆分销安装完成后报错(网络访问失败,请重试)_软件网络访问失败,请重试-程序员宅基地

文章浏览阅读4k次。管家婆分销ERP V3 安装后打开报错报错信息:网络访问失败请重试解决方法:方法一取消选择方法二链接: https://share.weiyun.com/5p7DYqJ (密码:6SNw)将文件替换到根目录付费技术支持QQ:204814945..._软件网络访问失败,请重试

【数字信号处理】关于实现FIR滤波器的一些问题_信号与fir滤波器长度不同-程序员宅基地

文章浏览阅读2.8k次。在知乎上看到了一个提问,稍微整理了一下关于这些问题的回答https://www.zhihu.com/question/29945169FIR其实就是下面这个公式,其中数组x[]为输入,数组h[]为滤波参数(已知),数组y[]为滤波输出:问题1“实现FIR滤波过程就是上面这个过程?这么简单?还是自己理解错了根本不是这么回事?(想确认:对于实现这块,上面的理解对不对)”从理论上,FIR就是上面这个过程,但是做成实际使用的系统会遇到各种问题阻止你实现这个公式。情况1:已知FIR滤波器在时域的序列这可_信号与fir滤波器长度不同

用JavaScript实现选项卡功能,当选择全选按钮的时候,下边的复选框所以按钮被选中,当取消一个复选框按钮时,全选框按钮失去效果,当再次把所有复选框按钮选择实现全选_、实现操作复选框,要求是可以选择部分选项,也可以全选,全选按钮的状态根据选中的选项个数自动变化,即全-程序员宅基地

文章浏览阅读779次。点击全选,让下边的按钮都被选中,以及复选框的交互简单上代码啦~~CSS样式<style> * { padding: 0; margin: 0; } .wrap { width: 300px; margin: 100px auto 0; } table { border-collapse: _、实现操作复选框,要求是可以选择部分选项,也可以全选,全选按钮的状态根据选中的选项个数自动变化,即全选按钮的状态回自动取消或者自动勾选。

http请求工具类HttpClientUtil(get使用body,post乱码问题解决)_((httpentityenclosingrequestbase) httppost.setenti-程序员宅基地

文章浏览阅读3.4k次。最近很多发送http请求的需求存在,书写下util1:配置需要的依赖在pom.xml中配置http相关依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <s..._((httpentityenclosingrequestbase) httppost.setentity(new inputstreamentity(i

随便推点

好记性不如烂笔头---Archlinux优化简介-程序员宅基地

文章浏览阅读818次。分析瓶颈要优化系统,先要找到性能瓶颈。通过分析系统配置能够获取这些信息。这里给出几条分析系统性能的简单方法: 运行大型软件(比如 openoffice、firefox)时,如果系统变卡,很可能是内存不足。以下命令用来查询内存使用信息(-/+buffers 一栏): $ free -m 如果开机时间很长,或者第一次加载某个程序十分缓慢,则很可能是硬盘太慢了..._archlinux mksquashfs

3DREM16P-7X/250YG24-8K4V比例减压阀放大器-程序员宅基地

文章浏览阅读106次。比例溢流阀是一种液压压力控制阀,它通过弹簧力的大小改变溢流压力大小变化。比例电磁铁作用在弹簧上的力可以按比例调整,所以就输入信号变化比例溢流阀的压力也会变化。普通溢流阀与比例溢流阀一样,都有一个阀芯,阀芯的一端是液压油产生的压力,另一端是机械力。普通溢流阀通过调节弹簧力,来调整液压压力。而比例溢流阀是电磁铁直接产生推力,作用在阀芯上,电磁铁上的输入电压可以在0-24伏之间变化,产生的推力就随之变化,从而得到连续变化的液压压力。

python文件操作(open()、write()、writelines()、read()、readline()、readlines()、seek()、os)_python open writeline-程序员宅基地

文章浏览阅读3.7k次,点赞4次,收藏23次。python文件操作(open()、write()、writelines()、read()、readline()、readlines()、seek()、os)_python open writeline

分布式限流实战--redis实现令牌桶限流_分布式令牌限流-程序员宅基地

文章浏览阅读9.5k次,点赞4次,收藏43次。这篇文章我们主要是分析一下分布式限流的玩法。 因为限流也是一个经典用法了。1.微服务限流随着微服务的流行,服务和服务之间的稳定性变得越来越重要。缓存、降级和限流是保护微服务系统运行稳定性的三大利器。缓存的目的是提升系统访问速度和增大系统能处理的容量,而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开,而有些场景并不能用缓存和降级来解决,比如稀缺资源、数据库的写操作、频繁的复杂查询,因此需有一种手段来限制这些场景的请求量,即限流。比如当我们设计了一个函数,准备上线_分布式令牌限流

【Linux】文件系统-程序员宅基地

文章浏览阅读1.7k次,点赞27次,收藏22次。了解磁盘的物理结构、磁盘的具体物理结构、逻辑抽象、软硬连接,动静态库

python实现ks算法_python, 在信用评级中,计算KS statistic值-程序员宅基地

文章浏览阅读456次。# -*- coding: utf-8 -*-import pandas as pdfrom sklearn.grid_search import GridSearchCVfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerfrom sklearn.u..._ks_statistic

推荐文章

热门文章

相关标签