套接字编程的总结
套接字编程的总结 第一篇
ServerSocket
是创建TCP服务端Socket的API。
服务器使用的TCP Socket对象(传入的端口,就是要公开的端口,一般称为监听(listen)端口)
注意:
accept:接起电话(服务器是电话铃响的这一方)
Socket对象:建立起的连接
close:挂电话(谁都可以挂)
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
注意:
注意:
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
对比以上长短连接,两者区别如下:
扩展了解:
基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。
由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。 一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求。
实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。
现在还遗留一个问题:
如果同时多个长连接客户端,连接该服务器,能否正常处理?
需要在IDEA配置客户端支持同时运行多个实例!
所以可以使用多线程解决长连接客户端不支持同时在线的问题:
将任务专门交给其他线程来处理,主线程只负责接受socket。
这里仅演示短连接,长连接和多线程在博主的个人仓库下:
TCP服务端:
TCP客户端:
套接字编程的总结 第二篇
fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -一。read() 的原型为:
fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。
套接字编程的总结 第三篇
由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。 基于Socket套接字的网络程序开发就是网络编程。
Socket套接字主要针对传输层协议,分为三类: 一、流套接字:使用传输层TCP协议 TCP的特点:有连接、可靠传输、面向字节流,全双工 对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情 况下,是无边界的数据,可以多次发送,也可以分开多次接收。
二、数据报套接字:使用传输层UDP协议 UDP的特点:无连接、不可靠传输、面向数据报,全双工 对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如一零零个字节,必须一 次发送,接收也必须一次接收一零零个字节,而不能分一零零次,每次接收一个字节。
三、原始套接字
套接字编程的总结 第四篇
只能处理IPv四的ip地址 不可重入函数 注意参数是struct in_addr
支持IPv四和IPv六 可重入函数 其中inet_pton和inet_ntop不仅可以转换IPv四的in_addr,还可以转换IPv六的in六_addr。
socket模型流程图
在Linux下创建socket
绑定端口号
socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;而客户端要用 connect() 函数建立连接。
套接字编程的总结 第五篇
一) 首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
二) 如果要读取的数据长度小于缓冲区中的数据长度,那么就不能一次性将缓冲区中的所有数据读出,剩余数据将不断积压,直到有 read()/recv() 函数再次读取。
三) 直到读取到数据后 read()/recv() 函数才会返回,否则就一直被阻塞。
这就是TCP套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。
TCP套接字默认情况下是阻塞模式
和C语言教程一样,我们从一个简单的“Hello World!”程序切入 socket 编程。
演示了 Linux 下的代码, 是服务器端代码, 是客户端代码,要实现的功能是:客户端从服务器读取一个字符串并打印出来。
服务器端代码 :
客户端代码 :
先编译 并运行:
正常情况下,程序运行到 accept() 函数就会被阻塞,等待客户端发起请求。接下来编译 并运行:
client 运行后,通过 connect() 函数向 server 发起请求,处于监听状态的 server 被激活,执行 accept() 函数,接受客户端的请求,然后执行 write() 函数向 client 传回数据。client 接收到传回的数据后,connect() 就运行结束了,然后使用 read() 将数据读取出来。需要注意的是: server 只接受一次 client 请求,当 server 向 client 传回数据后,程序就运行结束了。如果想再次接收到服务器的数据,必须再次运行 server,所以这是一个非常简陋的 socket 程序,不能够一直接受客户端的请求。
套接字编程的总结 第六篇
源文件 包含迭代的、无连接 TIME 服务器所用的代码。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。
该结构体即本篇博客开头的背景知识部分所提到的新的通用套接字地址结构体,兼容 IPv六。
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen)
用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。
uint三二 htonl(uint三二 hl)
htonl()将主机数转换成无符号长整型的网络字节顺序。本函数将一个三二位数从主机字节顺序转换成网络字节顺序。可类比于 ntohl()
int sendto ( socket s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen )
sendto() 指向一指定目的地发送数据,sendto() 适用于发送未建立连接的 UDP 数据包 (参数为 SOCK_DGRAM )。sendto() 用来将数据由指定的 socket 传给对方主机。
参数 s 为已建好连线的 socket,如果利用 UDP 协议则不需经过连线操作
参数 msg 指向欲连线的数据内容
参数 len 数据内容长度
参数 flags 一般设零
参数 to 用来指定欲传送的网络地址
参数 tolen 为 sockaddr 的结构长度
套接字编程的总结 第七篇
源文件 中定义的函数 passivesock 包含分配和绑定服务器套接口的细节。该函数通常作为库例程被其它程序调用(如 和 等)。
在本篇博客前面部分有讲解到 addrinfo 结构体,其中 hints 参数的 ai_flags 成员没有细说,在服务器库例程中又有涉及,故在此叙述下:
ai_flags参数:
AI_PASSIVE:设置了 AI_PASSIVE 标志,则
AI_CANONNAME:请求canonical(主机的official name)名字。如果设置了该标志,那么 res 返回的第一个 struct addrinfo 中的 ai_canonname 域会存储official name的指针。
AI_NUMERICHOST:阻止域名解析。
AI_NUMERICSERV:阻止服务名解析。
AI_V四MAPPED:当 ai_family 指定为AF_INT六(IPv六)时,如果没有找到IPv六地址,那么会返回IPv四-mapped IPv六 地址。
int setsockopt(int s, int level, int optname, const void * optval, socklen_toptlen)
函数说明:setsockopt() 用来设置参数 s 所指定的 socket 状态
参数 level 代表欲设置的网络层,一般设成 SOL_SOCKET 以存取 socket 层
参数 optname 代表欲设置的选项,有下列几种数值:
SO_DEBUG 打开或关闭排错模式
SO_REUSEADDR 允许在 bind () 过程中本地地址可重复使用
SO_TYPE 返回 socket 形态
SO_ERROR 返回 socket 已发生的错误原因
SO_DONTROUTE 送出的数据包不要利用路由设备来传输
SO_BROADCAST 使用广播方式传送
SO_SNDBUF 设置送出的暂存区大小
SO_RCVBUF 设置接收的暂存区大小
SO_KEEPALIVE 定期确定连线是否已终止.
SO_OOBINLINE 当接收到 OOB 数据时会马上送至标准输入设备
SO_LINGER 确保数据安全且可靠的传送出去.
参数 optval 代表欲设置的值
参数 optlen 则为optval 的长度
int bind(int socket, const struct sockaddr *address, socklen_t address_len)
通过 socket 系统调用创建的文件描述符并不能直接使用,TCP/UDP协议中所涉及的协议、IP、端口等基本要素并未体现,而 bind() 系统调用就是将这些要素与文件描述符关联起来。
int listen(int socket, int backlog)
使用 socket 系统调用创建一个套接字时,它被假设是一个主动套接字(客户端套接字),而调用 listen() 系统调用就是将这个主动套接字转换成被动套接字,指示内核应接受指向该套接字的连接请求。返回值为 零 则成功,-一 表示失败并设置 errno 值。
socket:socket 监听文件描述符。
backlog:设置未完成连接队列和已完成连接队列各自的队列长度(注意:不同的系统对该值的解释会存在差异)。 Linux 系统下,SYN QUEUE 队列长度阈值存放在 /proc/sys/net/ipv四/tcp_max_syn_backlog 文件中,ACCEPT QUEUE 队列长度阈值存放在 /proc/sys/net/core/somaxconn 文件中。两个队列长度的计算公式如下:
套接字编程的总结 第八篇
注:三次握手,指在建立链接过程中,客户端先向服务端发出syn请求,然后服务端对客户端进行ack回复及syn请求,客户端再进行ack请求,来回了三次才能确定可以建立链接(因为TCP协议是面向连接的,所以必须确定客户端和服务端均在线)
以上函数的实现及解析会在代码中实现关于
直接上代码,代码里面详细的解析
阿鲤在这里直接给大家github的链接,里面有以下四种类型
注意:因为tcp协议存在三次握手,在传输信息时会服务端每次都会新建立一个新的socket所以在多个客户端向服务端发出请求时,会覆盖原先的socket的操作句柄(fd),导致之前的客户端链接失效,所以需要多线程或多进程实现
套接字编程的总结 第九篇
为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数(本质就是打开网络文件),指定期望通信的协议类型(使用IPV四的TCP、使用IPV六的UDP、Unix域字节流协议等)。
参数说明:
domain参数:指明协议族,即你想要使用什么协议(IPV四、IPV六...),它是下列表格中的某个常值。该参数也往往被称为协议域。
规定:我们接下来所使用的套接字所采用的协议都是AF_INET(IPV四协议)
type参数:指明套接字的类型,它是下列表格中的某个常值。
如果你是要TCP通信的话,就要是要SOCK_STREAM作为类型,UDP就使用SOCK_DGRAM作为类型。
protocol参数:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为零就可以了,设置为零表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。
返回值说明:
套接字创建成功返回一个文件描述符,创建失败返回-一,同时错误码会被设置。
bind函数是把一个协议地址赋予一个套接字。
参数说明:
sockfd参数:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
addr参数:这个参数是指向一个特定于协议的地址结构的指针。里面包含了协议族、端口号、IP地址等。(见下一节sockaddr结构中的关于)
addrlen参数:是该协议的地址结构的长度。
返回值说明:
绑定成功返回零,绑定失败返回-一,同时错误码会被设置。
listen函数仅由TCP服务器调用,表明服务器对外宣告它愿意接受连接请求,它做两件事:
一.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说将调用connect发起连接的客户端套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。简单来说,服务器调用listen函数,就是告诉客户端你可以连接我了。
二.第二个参数规定了内核应该为相应的套接字排队的最大连接个数。backlog提供了一个提示,提示系统该进程要入队的未完成连接的请求数量。其实际由系统决定,对于TCP而言,默认是一二八。
一旦队列满了,系统就会拒绝多余的连接请求,所有backlog的值应该基于服务器期望负载和处理量来选择,其中处理量是指接受连接请求与启动服务的数量。
一旦服务器调用了listen,所用的套接字就能接受连接请求。使用accept函数获得的连接请求并建立连接。
返回值:成功返回零,失败返回-一;
本函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。
accept函数是由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程将被投入睡眠。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。我们常常称它的第一个参数为监听套接字(listening socket) 描述符(由socket创建,随后用作bind和listen的第一个参数的描述符),称它的返回值为已连接套接字(connected socket) 描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
总的来说,函数accept所返回的文件描述符是新的套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持监听状态并接受其他连接请求。
TCP客户用connect函数来建立与TCP服务器的连接。
参数说明:
sockfd参数:是由socket函数返回的套接字描述符,第二个以及第三个参数分别是指向套接字地址结构的指针和该结构的大小。
在connect中指定的地址是我们想要与之通信服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
返回值说明:
若成功则为零,若出错则为-一;
从关于的套接字函数接口来看,bind函数、accept函数和conect函数都有一个struct sockaddr的结构体指针,我们在关于参数的时候也已经说了,这种结构是指向一个特定于协议的地址结构的指针。里面包含了协议族、端口号、IP地址等。
在网络通信的时候,其标准方式有多种,比如:IPV四套接字地址结构——struct sockaddr_in,Unix域套接字地址结构——struct sockaddr_un;前者属于网络通信,后者属于域间通信;
也就是说我们的套接字接口就这么一套,但是通信方式确有多种,你只需要给这个结构体(struct sockaddr)传输你想要的通信方式即可;其实也不难看出,这种就类似于多态,所有的通信方式都是子类,struct sockaddr就是父类,父类指向不同的子类,就使用不同的方法;
我们要做的就是在使用的时进行强制类型转换即可;可能你会想到在C语言中有一个指针void*,它的功能就是可以接受任意类型的指针,再进行强转也可以。但是,早期在设计的时候还没有void*这种指针,所以这种用法一直延续至今。
大多数套接字函数都需要指向套接字地址结构的指针作为参数。每个协议族都定义了它自己的套接字地址结构。这些地址结构的名字均已sockaddr_开头。
由于通信方式的种类有多种,套接字接口只有一套,如果给每个通信方式都设计一套接口,单从技术的角度来说,完全是可以的。但是从使用者和学习者来讲,无疑是增加了负担。所以早期在设计的时候,就单独设计了一个通用的套接字地址结构,我们只要给这个通用的套接字地址结构传入不同的套接字地址结构,然后进行强转。在地址结构中给到我们想要通信的IP地址、端口号以及所采用的协议族。
其中三个结构里都包含了 __SOCKADDR_COMMON 这个宏,我们先把它的定义找到;
这三个结构的第一个字段都是一个unsigned short int 类型,只不过用宏来定义了三个不同的名字,至此第一个结构就清楚了,在一般环境下(short一般为二个字节),整个结构占用一六个字节,变量sa_family占用二个字节,变量sa_data 保留一四个字节用于保存IP地址信息。
接着我们发现第二个结构中还有in_port_t和struct in_addr两个类型没有定义,继续找下去吧:
这么看来sockaddr_in这个结构也不复杂,除了一开始的二个字节表示sin_family,然后是二个字节的变量sin_port表示端口,接着是四个字节的变量sin_addr表示IP地址,最后是八个字节变量sin_zero填充尾部,用来与结构sockaddr对齐。
那接下来我们整理一下,为了看的清楚,部分结构使用伪代码,不能通过编译,主要是方便理解:
附图:源码结构
套接字编程的总结 第一零篇
当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
套接字编程的总结 第一一篇
举个栗子:
端口被占用的问题:
如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这种情况也叫端口被占用。对于java进程来说,端口被占用的常见报错信息如下:
此时需要检查进程B绑定的是哪个端口,再查看该端口被哪个进程占用。以下为通过端口号查进程的方式:
在cmd输入 netstat -ano | findstr 端口号 ,则可以显示对应进程的pid。如以下命令显 示了八八八八进程的pid
在任务管理器中,通过pid查找进程 解决端口被占用的问题:
套接字编程的总结 第一二篇
网络编程的核心,是Socket API,这是一个由操作系统给应用程序提供的网络编程API。
并且我们认为Socket API是和传输层密切相关的。
Socket套接字主要针对传输层协议分为以下几类:
流套接字:使用传输层TCP协议
数据报套接字:使用传输层UDP协议
无连接、有连接:
打电话就是有连接的,需要连接建立了才能通信。连接建立需要对方来接收,如果连接没有建立好,就通信不了。
发短信、发微信就是无连接的。
不可靠传输、可靠传输:
网络环境天然就是复杂的,不可能保证传输的数据一零零%能够到达。发送方能知道自己的消息是发送过去了还是丢了,就是可靠\不可靠传输。
面向字节流、面向数据报:
数据传输就和文件读写类似,“流式”的,就叫面向字节流
数据传输以一个个的“数据报”(可能是若干字节,带有一定格式的)为基本单位,就叫面向数据报。
全双工、半双工:
一个通信通道,可以双向传输,既可以发送也可以接收就叫做全双工。
只能单向传输的就叫做半双工。
Java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramPacket作为发送或接收的UDP数据报,对于一次发送及接收UDP数据报的流程如下:
以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就只有请求,没有响应。对于一个服务器来说,重要的是提供多个客户端的请求处理及响应,流程如下:
首先先了解一些注意事项:
一.客户端和服务器:开发时,一般是基于一个主机开启两个进程作为客户端和服务器,但真实的场景一般都是不同主机。
二.注意目的IP和目的端口号,标识了一次数据传输时要发送数据的终点主机和进程。
编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议, 也需要考虑,这块我们在后续来说明如何设计应用层协议。
四.端口被占用的问题:
如果一个进程A已经绑定了一个端口,再启动一个进程B绑定该端口,就会报错,这就叫端口被占用。对于Java进程来说,端口被占用的常见报错信息如下:
在cmd输入:netstat -ano | findstr 端口号 就可以显示对应进程的pid,然后在任务管理器中通过pid查找进程。
解决方法:
如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B。
如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
在操作系统中,把这个socket对象也当成是一个文件来处理的,相当于是文件描述符表上的一项。只不过普通文件对应的设备是硬盘,而socket文件对应的设备是网卡。
DatagramSocket构造方法:
DatagramSocket 方法:
DatagramPacket是UDP Socket发送和接收的数据报。
DatagramPacket 构造方法:
DatagramPacket 方法:
普通的服务器:收到请求,根据请求计算响应,返回响应。
而echo server(回显服务器)省略了其中的根据请求计算响应,请求是啥,就返回啥。
先来看一遍完整代码:
我们一点一点来解析:
在操作系统内核中, 使用了一种特殊的叫做 _socket_ 这样的文件来抽象表示网卡,因此进行网络通信, 势必需要先有一个 socket 对象。
同时对于服务器来说, 创建 socket 对象的同时, 要让他绑定上一个具体的端口号,如果是操作系统随机分配的端口, 此时客户端就不知道这个端口是啥了, 也就无法进行通信了。
对于UDP来说,传输数据的基本单位是DatagramPacket,并且用一个while循环来表示循环接收请求,用DatagramPacket来表示接收到的,然后再用receive把这个数据报给网卡接收到。
此时的DatagramPacket是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,通过构造字符串的方式来存到request里面去。
之前给的最大长度是四零九六,但是这里的空间不一定用满了,可能只用了一小部分,因此就通过getLength获取到实际的数据报长度,只把这个实际的有效部分给构造成字符串即可。
紧接着我们用一个process方法来表示服务器的响应。实际开发中这个部分是最重要的,服务器的响应是整个网络编程最核心的部分之一。
获取到客户端的ip和端口号(这两个信息本身就在requestpacket中)
通过send方法把responsePacket方法里面的信息传出去。
主要的工作流程:
一.读取请求并解析
二.根据请求计算相应
三.构造响应并且写回客户端
一次通信,需要有两个ip,两个端口,客户端的ip是,客户端的端口是系统自动分配的,服务器ip和端口需要告诉客户端,才能顺利把消息发给服务器。
先来看完整代码:
通过socket,IP和端口我们才能和服务器端连接起来。
在客户端中,需要用户自己输入,获取到用户的request后,需要打包成requestPacket然后通过发送给服务器。
完成上一步后,等待服务器的响应,到客户端这边用receive接收,类型为responsePacket。最后再转换成String类型的response打印出来。
客户端发送给服务器后,就进入阻塞等待,这里的receive能阻塞,是因为操作系统原生提供的API就是阻塞的函数,这里的阻塞不是Java实现的,而是系统内核里实现的。
同时最后的main函数中,应该指定好ip和端口号,以便客户端能访问到服务器端。
同时也可以打开这个选项,同时开启多个客户端,共用一个服务器。
针对上述的程序,来看看端口冲突是什么效果,一个端口只能被一个进程使用,如果有多个就不行。
TCP和UDP的差别还是有不少的,比如一个有连接一个无连接,一个是可以直接发送,一个需要数据报打包发送。
ServerSocket 是创建TCP服务端Socket的API。
构造方法:
方法:
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
构造方法:
方法:
在这里有serverSocket和clientSocket,这两个socket是不同的,serverSocket接收端口号和Ip地址,然后通过clientSocket和客户端连接。因为需要连接上后才能发送消息,所以每用到一个clientSocket就会有一个客户端连接上来,都会返回/创建一个Socket对象,Socket就是文件,每次创建一个clientSocket对象,就要占用一个文件描述符表的位置。
因此这里的socket需要释放。前面的socket都没有释放,一方面这些socket生面周期更长,另一方面这些socket也不多。但是此处的clientSocket数量多,每个客户端都有一个,生命周期也更短。
accept如果没有连接到客户端,就会一直阻塞。
要注意,TCP server一次性只能处理一个客户端
通过clientSocket进行processConnection进行了具体的连接以后,通过try with resources来完成InputStream和outputStream来完成字节流的传输。
通过InputStream接收到服务器端的数据后,再通过scanner写入到request,request传入到process方法返回服务器相应的数据。接下来应该用outputStream来写入服务器返回的数据,但是outputStream中并没有write String这样的功能,所以此处用println来写入。
并且println中会在发送的数据后面自动带上\n换行,TCP协议是面向字节流的协议,但是接收方如何知道这一次一共需要读多少字节呢?这就需要我们再数据传输中进行明确的规定:
此处代码中,隐式约定使用了\n来作为当前代码的请求、相应分割约定。
所以这里的println也可以当做是服务器发送给客户端的发送行为。
通过socket来接收服务器的ip和端口号。
和服务器不同的是,客户端方需要读取用户自己输入的数据,所以通过来接收用户输入的,但是最终是需要用到流式传输中,所以需要用try with resources来包含InputStream和outputStream。
通过request接收到用户输入的数据后,用PrintWriter来写入,再通过println来发送。并且以防万一,我们用flush来刷新缓冲区避免数据传输失败。
等待服务器返回消息后,用responseScanner来接收InputStream传输的数据,再打印出来。
当前咱们的服务器同一时刻只能给一个客户端提供服务,这是不科学的。当前启动服务器后,先后启动两个客户端,客户端一可以看到正常的上线提示,但是客户端二没有任何提醒。当结束客户端一后,客户端二马上显示上线。
当客户端连接上服务器之后,代码执行到processConnection这个方法中的while循环了,此时意味着,只要这个循环不结束,processConnection方法就结束不了。进一步的也就无法调用到第二次的accept。
解决办法就是:使用多线程
其实修改的部分很小,只要在启动连接的时候,作为一个单独的线程启动就大功告成。
但是呢,这里的多线程版本的程序,最大的问题就是可能会涉及到频繁申请释放线程,当客户端数量足够多,也会造成很大的资源消耗。
所以解决办法就是:使用线程池
通过线程池的方法,就能进一步减少消耗。
但是呢,如果客户端都在响应,就算使用了线程池了但是还是不够,而且如果客户端非常多,客户端连接迟迟不断开,就会导致机器上有很多线程。
解决办法就是:IO多路复用,IO多路转接
给这个线程安排一个集合,这个集合就放了一堆连接。这个线程就来负责监听这个集合,哪个连接有数据来了,线程就处理哪个连接。这其实就是因为,虽然连接有很多很多,但是这些连接的请求并非完全严格的同时,总还是有先后的。
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。
对比以上长短连接,两者区别如下:
建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。
套接字编程的总结 第一三篇
发送信息:应用层-》传输层-》网络层-》链路层-》物理层
接受信息:物理层-》链路层-》网络层-》传输层-》应用层
如下图,假设你要使用扣扣发送一个hello;
注意:每一层都会记录上层所使用的协议
套接字编程的总结 第一四篇
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数据报,一次接收全部的数据报。
java中使用UDP协议通信,主要基于 DatagramSocket
类来创建数据报套接字,并使用 DatagramPacket
作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:
DatagramSocket
是UDP Socket,用于发送和接收UDP数据报。
注意:
UDP服务器(Server):采用一个固定端口,方便客户端(Client)进行通信; 使用 DatagramSocket(int port)
,就可以绑定到本机指定的端口,此方法可能有错误风险,提示该端口已经被其他进程占用。
UDP客户端(Client):不需要采用固定端口(也可以用固定端口),采用随机端口; 使用 DatagramSocket()
,绑定到本机任意一个随机端口
注意:
一旦通信双方逻辑意义上有了通信线路,双方地位就平等了(谁都可以作为发送方和接收方)
发送方调用的就是 send()
方法,接收方调用的就是 receive()
方法
通信结束后,双方都应该调用 close()
方法进行资源回收
DatagramPacket
是UDP Socket发送和接收的数据报。
这个类就是定义的报文包:通信过程中的数据抽象
可以理解为:发送/接受的一个信封(五元组+信件)
注意:
注意:
InetSocketAddress ( SocketAddress 的子类 )构造方法:
以下仅展示部分代码,完整代码可以看博主的gitee仓库:
UDP客户端:
UDP服务端:
自定义的日志类(记得导入此类):
套接字编程的总结 第一五篇
服务端
客户端
结果: 注意: 一、因为TCP是有连接通信,所以我们要用多线程的方式来接收客户端的请求,否则一个服务端只能链接客户端,当我们用多线程的方式来接待客户端,此时一个服务端就可以调用多个线程来对应的响应多个客户端; 二、为什么UDP版本不需要使用多线程? 因为UDP是无连接通信,客户端不需要服务端确认接收也可以发送,简言之就是UDP没有TCP依赖性强;UDP就像是发信息,直接发送内容即可,不需要确认对方是否正在盯着手机看。但是TCP就像是打电话,我们拨电话之后,要等到对方接电话才可以说谈话内容,如果对方不接电话,我们就没法进行沟通。在TCP版本的情况下运用多线程,就是我们拨电话之后,对方派A和我们沟通,又来一个电话,对方派B来沟通。
套接字编程的总结 第一六篇
源文件 中定义的函数 connectsock 分配套接字和连接该套接字,该函数通常作为库例程被其他程序调用(如 和 等)。
库例程:例程的作用类似于函数,但含义更为丰富一些。例程是某个系统对外提供的功能接口或服务的集合。故一个库例程可以理解为一个库函数,可以方便地被别的程序调用的库函数。
IPv四 的 sockaddr_in 结构体:指明了端点地址,其包括用来识别地址类型的 二 字节字段(必须为AF_INET),还有一个 二 字节的端口号字段,一个 四 字节的 具体 IP 地址的字段,还有一个未使用的 八 字节字段。
IPv六 的 sockaddr_in六 结构体
family 参数根据协议族的不同,选择 AF_INET 或 AF_INET六。
通用套接口地址结构
**新的通用套接口地址结构:**兼容 IPv六 地址
考试中的库例程程序相当于把复习资料里的示例程序更加抽象化了。
包含在 中,主要在网络编程解析时使用。
getaddrinfo(const char *restrict nodename, const char *restrict servname, const struct addrinfo *restrict hints, struct addrinfo **restrict res)
返回值零表示成功,非零表示错误。
该方法是在 IPv六 中引入,协议无关,即可用于 IPv四 也可用于 IPv六。getaddrinfo() 函数可以处理名称到地址以及服务到端口的这两种转换,返回的是一个 struct addrinfo 的结构体指针而不是一个地址清单。
nodename 参数:主机名,或者是点分十进制地址串(IPv四),或一六进制串(IPv六)
servname 参数:服务名,可以是端口号,也可以是已定义的服务名称如“https”等
hints 参数:指向用户指定的 struct addrinfo 结构体,只能设定其中 ai_family, ai_socktype, ai_protocol 和 ai_flags 四个域,其他域必须为 零 或者是 NULL。其中:
ai_family:指定返回地址的协议簇,AF_INET(IPv四), AF_INET六(IPv六), AF_UNSPEC(IPv四 and IPv六)
ai_socktype:用于设定返回地址的 socket 类型,常用有 SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, 设置为零表示所有类型等
ai_protocol:指定协议,常用有 IPPROTO_TCP、IPPROTO_UDP等,设置为 零 表示所有协议
ai_flags:附加选项,AI_PASSIVE, AI_CANONNAME等(不考故不赘述
res 参数:获取一个指向存储结果的 struct addrinfo 结构体,使用完成后调用 freeaddrinfo() 释放存储结果空间
释放 addrinfo 结构体动态内存;
freeaddrinfo(struct addrinfo *ai)
socket 编程需要基于一个文件描述符,即 socket 文件描述符。socket 系统调用就是用来创建 socket 文件描述符。成功返回零,失败返回-一并设置 errno 值。
int socket(int domain, int type, int protocol);
创建主动套接字的一方(客户端)调用 connect() 系统调用,可建立与被动套接字的一方(服务端)的连接。成功返回零,失败返回-一并设置 errno 值。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
**sockfd 参数:**连接文件描述符
addr 参数:指定协议的地址结构体指针
addrlen 参数:协议地址结构体长度
close() 一个TCP套接字的默认行为是把该套接字标记为关闭,此后不能再对该文件描述符进行读写操作。TCP协议将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常的TCP连接终止序列。成功返回零,失败返回-一并设置 errno 值。
int close(int fd)
char *gai_strerror(int error)
getaddrinfo出错时返回非零值,gai_strerror根据返回的非零值返回指向对应的出错信息字符串的指针。
char * strerror(int errnum)
从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。
注:本篇博客的所有程序都省略了头文件引用
注意:return 函数有没有括号 “()” 都是正确的,为了简明,一般省略不写
套接字编程的总结 第一七篇
网络上的主机通过不同的进程,以编程的方式实现网络通信,我们称之为网络编程。 我们只要满足不同的进程即可,所以即便是同一个主机,只要是不同的进程,基于网络传输数据,也是属于网络编程
在一次网络数据传输时,有发送端、接收端、收发端三方 注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
一般来说,获取一个网络资源,涉及到两次网络数据传输: 第一次:请求数据的发送; 第二次:响应数据的发送;
服务端:提供服务的一方进程,成为服务端,可以提供对外服务; 客户端:获取服务的一方进程,成为客户端; 对于服务来说,一般提供一、客户端获取服务资源。二、客户端保存资源在服务端
套接字编程的总结 第一八篇
源文件 包含针对 TIME 服务的简单 UDP 客户程序。 本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。
size_t write(int flides, const void *buf, size_t nbytes)
write系统调用,将缓存区buf中的前nbytes字节写入到与文件描述符flides有关的文件中,write系统调用返回的是实际写入到文件中的字节数。
uint三二_t ntohl(uint三二_t netlong);
ntohl() 将一个无符号长整形数从网络字节顺序转换为主机字节顺序,返回一个以主机字节顺序表达的数。
struct tm *localtime(const time_t *timer)
把从一九七零-一-一零点零分到当前时间系统所偏移的秒数时间转换为本地时间。
timer 是指向表示日历时间的 time_t 值的指针,timer的值被分解为 tm 结构,并用本地时区表示。
char *asctime(const struct tm *timeptr)
把 timeptr 指向的 tm 结构体中储存的时间转换为字符串。
套接字编程的总结 第一九篇
源文件 包含针对 DAYTIME 服务的简单 TCP 客户程序。本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。
ssize_t read [一] (int fd, void *buf, size_t count);
read() 会把参数 fd 所指的文件传送 count 个字节到 buf 指针所指的内存中。若参数count 为零,则 read() 不会有作用并返回 零。返回值为实际读取到的字节数,如果返回零,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。
套接字编程的总结 第二零篇
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
Socket是站在应用层,做网络编程很重要的一个概念
传输层、网络层、数据链路层、物理层 都是通过OS+硬件来提供服务的,而应用层要享受OS提供的网络服务,需要通过OS提供的服务窗口(Socket)来享受服务。
拓展:
OS原生的提供的系统调用(Linux上的网络编程):
套接字编程的总结 第二一篇
字节序:cpu在内存中对数据存储的顺序,并且主机字节序分别为大端字节序和小端字节序, 小端字节序:低地址存低位 eg:X八六架构 大端字节序:低地址存高位 eg:MIPS架构 在网络通信中并不知道对端主机是大端还是小端因此两个不同主机字节的主机在进行通信时会造成数据二义,字节序对网络通信造成影响主要针对存储大于一个字节的类型(int一六_t,int三二_t,short,int,long,float,double); 为了避免因为主机字节序不同导致的数据二义性,因此规定网络通信使用统一字节标准,把这个统一的标准字节序称为网络字节序:大端字节序,所以在编写网络通信程序中,若是传输大于一个字节类型的数据,就需要考虑字节序的转换gong
注意:可使用联合体使用大小端的判断
字节序转换接口:
uint三二_t htonl(uint三二_t hostlong);//把三二位主机字节序转换成网络字节序
uint一六_t htons(uint一六_t hostshort);
uint三二_t ntohl(uint三二_t netlong);
uint一六_t ntohs(uint一六_t netshort);//把一六位网络字节序转换成主机字节序
套接字编程的总结 第二二篇
基本TCP客户/服务器通信流程如下,主要为了大家能够更好的理解
配有代码详解图
TCP服务端代码入下:
详解:
单进程版我们一般不用
详解:
对于上面的所给的案例,多线程和线程池版本没有再去仔细的讲解,首先基本的通信过程都是一样的,只要对进程和线程相关的函数掌握以及相关知识点的概念掌握很熟练的话,理解起来很容易。
套接字编程的总结 第二三篇
源文件 包含 ECHO 服务器的代码,它使用并发进程为多个客户提供并发服务。 源文件 包含 ECHO 服务器的代码,它使用迭代的、无连接算法为多个客户提 供服务。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。
pid_t fork( void)
返回值: 若成功调用一次则返回两个值,子进程返回零,父进程返回子进程ID;否则,出错返回-一。
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程。fork 函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回 零 值而父进程中返回子进程 ID。 子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本,即存储空间的“副本”,意味着父子进程间不共享这些存储空间。子进程有了独立的地址空间,故无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。
套接字编程的总结 第二四篇
源文件 包含针对 ECHO 服务的简单 TCP 客户程序。 源文件 包含针对 ECHO 服务的简单 UDP 客户程序。 本题程序调用了自定义例程库函数 connectsock 分配套接口和连接该套接口。
char *fgets(char *str, int n, FILE *stream)
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-一) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
参数:
int fputs(const char *str, FILE *stream)
把字符串写入到指定的流 stream 中,但不包括空字符。
参数:
套接字编程的总结 第二五篇
将一个客户端的套接字关联上一个地址没有多少新意,可以让系统选一个默认的地址。然而,对于服务器,需要给一个接收客户端请求的服务器套接字关联上一个众所周知的地址。客户端应有一种方法来发现连接服务器所需要的地址,最简单的方法就是服务器保留一个地址并且注册在/etc/services或者某个名字服务中。
使用bind函数来关联地址和套接字。
对于使用的地址有以下一些限制。
对于因特网域,如果指定IP地址为INADDR_ANY(中定义的),套接字端点可以被绑定到所有的系统网络接口上。这意味着可以接收这个系统所安装的任何一个网卡的数据包。在下一节中可以看到,如果调用 connect 或 listen,但没有将地址绑定到套接字上,系统会选一个地址绑定到套接字上。
可以调用getsockname函数来发现绑定到套接字上的地址。
调用 getsockname 之前,将 alenp 设置为一个指向整数的指针,该整数指定缓冲区sockaddr 的长度。返回时,该整数会被设置成返回地址的大小。如果地址和提供的缓冲区长度不匹配,地址会被自动截断而不报错。如果当前没有地址绑定到该套接字,则其结果是未定义的。
如果套接字已经和对等方连接,可以调用getpeername函数来找到对方的地址。
除了返回对等方的地址,函数getpeername和getsockname一样。
如果要处理一个面向连接的网络服务(SOCK_STREAM或SOCK_SEQPACKET),那么在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之间建立一个连接。使用connect函数来建立连接。
在connect中指定的地址是我们想与之通信的服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
当尝试连接服务器时,出于一些原因,连接可能会失败。要想一个连接请求成功,要连接的计算机必须是开启的,并且正在运行,服务器必须绑定到一个想与之连接的地址上,并且服务器的等待连接队列要有足够的空间(后面会有更详细的关于)。因此,应用程序必须能够处理connect返回的错误,这些错误可能是由一些瞬时条件引起的。
如果套接字描述符处于非阻塞模式,那么在连接不能马上建立时,connect将会返回−一并且将errno设置为特殊的错误码EINPROGRESS。应用程序可以使用poll或者select来判断文件描述符何时可写。如果可写,连接完成。
connect函数还可以用于无连接的网络服务(SOCK_DGRAM)。这看起来有点矛盾,实际上却是一个不错的选择。如果用SOCK_DGRAM套接字调用connect,传送的报文的目标地址会设置成connect调用中所指定的地址,这样每次传送报文时就不需要再提供地址。另外,仅能接收来自指定地址的报文。
服务器调用listen函数来宣告它愿意接受连接请求。
参数backlog提供了一个提示,提示系统该进程所要入队的未完成连接请求数量。其实际值由系统决定,但上限由中的SOMAXCONN指定。
一旦队列满,系统就会拒绝多余的连接请求,所以backlog的值应该基于服务器期望负载和处理量来选择,其中处理量是指接受连接请求与启动服务的数量。
一旦服务器调用了listen,所用的套接字就能接收连接请求。使用accept函数获得连接请求并建立连接。
函数accept所返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持可用状态并接收其他连接请求。
如果不关心客户端标识,可以将参数addr和len设为NULL。否则,在调用accept之前,将addr参数设为足够大的缓冲区来存放地址,并且将len指向的整数设为这个缓冲区的字节大小。返回时,accept会在缓冲区填充客户端的地址,并且更新指向len的整数来反映该地址的大小。
如果没有连接请求在等待,accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式, accept会返回−一,并将errno设置为EAGAIN或EWOULDBLOCK。
本文中讨论的所有平台都将EAGAIN定义为EWOULDBLOCK。
如果服务器调用accept,并且当前没有连接请求,服务器会阻塞直到一个请求到来。另外,服务器可以使用poll或select来等待一个请求的到来。在这种情况下,一个带有等待连接请求的套接字会以可读的方式出现。
尽管可以通过read和write交换数据,但这就是这两个函数所能做的一切。如果想指定选项,从多个客户端接收数据包,或者发送带外数据,就需要使用六个为数据传递而设计的套接字函数中的一个。
三个函数用来发送数据,三个用于接收数据。首先,考查用于发送数据的函数。
最简单的是send,它和write很像,但是可以指定标志来改变处理传输数据的方式。
类似write,使用send时套接字必须已经连接。参数buf和nbytes的含义与write中的一致。
然而,与write不同的是,send支持第四个参数flags。图一六-一三总结了这些标志。
即使send成功返回,也并不表示连接的另一端的进程就一定接收了数据。我们所能保证的只是当send成功返回时,数据已经被无错误地发送到网络驱动程序上。
对于支持报文边界的协议,如果尝试发送的单个报文的长度超过协议所支持的最大长度,那么send会失败,并将errno设为EMSGSIZE。对于字节流协议,send会阻塞直到整个数据传输完成。函数sendto和send很类似。区别在于sendto可以在无连接的套接字上指定一个目标地址。
对于面向连接的套接字,目标地址是被忽略的,因为连接中隐含了目标地址。对于无连接的套接字,除非先调用connect设置了目标地址,否则不能使用send。sendto提供了发送报文的另一种方式。
通过套接字发送数据时,还有一个选择。可以调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据,这和writev函数很相似。
定义了msghdr结构,它至少有以下成员:
函数recv和read相似,但是recv可以指定标志来控制如何接收数据。
当指定MSG_PEEK标志时,可以查看下一个要读取的数据但不真正取走它。当再次调用read或其中一个recv函数时,会返回刚才查看的数据。
对于SOCK_STREAM套接字,接收的数据可以比预期的少。MSG_WAITALL标志会阻止这种行为,直到所请求的数据全部返回,recv函数才会返回。对于SOCK_DGRAM和SOCK_SEQPACKET套接字,MSG_WAITALL 标志没有改变什么行为,因为这些基于报文的套接字类型一次读取就返回整个报文。
如果发送者已经调用shutdown来结束传输,或者网络协议支持按默认的顺序关闭并且发送端已经关闭,那么当所有的数据接收完毕后,recv会返回零。
如果有兴趣定位发送者,可以使用recvfrom来得到数据发送者的源地址。
如果addr非空,它将包含数据发送者的套接字端点地址。当调用recvfrom时,需要设置addrlen参数指向一个整数,该整数包含addr所指向的套接字缓冲区的字节长度。返回时,该整数设为该地址的实际字节长度。
因为可以获得发送者的地址,recvfrom通常用于无连接的套接字。否则,recvfrom等同于recv。
为了将接收到的数据送入多个缓冲区,类似于readv,或者想接收辅助数据,可以使用recvmsg。
recvmsg用msghdr结构(在sendmsg中见到过)指定接收数据的输入缓冲区。可以设置参数flags来改变recvmsg的默认行为。返回时,msghdr结构中的msg_flags字段被设为所接收数据的各种特征。(进入recvmsg时msg_flags被忽略。)recvmsg中返回的各种可能值总结在图一六-一五中。
套接字机制提供了两个套接字选项接口来控制套接字行为。一个接口用来设置选项,另一个接口可以查询选项的状态。可以获取或设置以下三种选项。
可以使用setsockopt函数来设置套接字选项。
参数 level 标识了选项应用的协议。如果选项是通用的套接字层次选项,则 level 设置成SOL_SOCKET。否则,level设置成控制这个选项的协议编号。对于TCP选项,level是IPPROTO_TCP,对于IP,level是IPPROTO_IP。图一六-二一总结了Single UNIX Specification中定义的通用套接字层次选项。
参数val根据选项的不同指向一个数据结构或者一个整数。一些选项是on/off开关。如果整数非零,则启用选项。如果整数为零,则禁止选项。参数len指定了val指向的对象的大小。
可以使用getsockopt函数来查看选项的当前值。
参数lenp是一个指向整数的指针。在调用getsockopt之前,设置该整数为复制选项缓冲区的长度。如果选项的实际长度大于此值,则选项会被截断。如果实际长度正好小于此值,那么返回时将此值更新为实际长度。
带外数据(out-of-band data)是一些通信协议所支持的可选功能,与普通数据相比,它允许更高优先级的数据传输。带外数据先行传输,即使传输队列已经有数据。TCP 支持带外数据,但是UDP不支持。套接字接口对带外数据的支持很大程度上受TCP带外数据具体实现的影响。
TCP将带外数据称为紧急数据(urgent data)。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。为了产生紧急数据,可以在三个send函数中的任何一个里指定MSG_OOB标志。如果带MSG_OOB标志发送的字节数超过一个时,最后一个字节将被视为紧急数据字节。
如果通过套接字安排了信号的产生,那么紧急数据被接收时,会发送SIGURG信号。在节和节中可以看到,在fcntl中使用F_SETOWN命令来设置一个套接字的所有权。如果fcntl中的第三个参数为正值,那么它指定的就是进程ID。如果为非-一的负值,那么它代表的就是进程组ID。因此,可以通过调用以下函数安排进程接收套接字的信号:
F_GETOWN命令可以用来获得当前套接字所有权。对于F_SETOWN命令,负值代表进程组ID,正值代表进程ID。因此,调用
将返回owner,如果owner为正值,则等于配置为接收套接字信号的进程的ID。如果owner为负值,其绝对值为接收套接字信号的进程组的ID。
TCP支持紧急标记(urgent mark)的概念,即在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否已经到达紧急标记,可以使用函数sockatmark。
当下一个要读取的字节在紧急标志处时,sockatmark返回一。
当带外数据出现在套接字读取队列时,select函数会返回一个文件描述符并且有一个待处理的异常条件。可以在普通数据流上接收紧急数据,也可以在其中一个recv函数中采用MSG_OOB标志在其他队列数据之前接收紧急数据。TCP队列仅用一个字节的紧急数据。如果在接收当前的紧急数据字节之前又有新的紧急数据到来,那么已有的字节会被丢弃。
通常,recv 函数没有数据可用时会阻塞等待。同样地,当套接字输出队列没有足够空间来发送消息时,send 函数会阻塞。在套接字非阻塞模式下,行为会改变。在这种情况下,这些函数不会阻塞而是会失败,将errno设置为EWOULDBLOCK或者EAGAIN。当这种情况发生时,可以使用poll或select来判断能否接收或者传输数据。
在基于套接字的异步I/O中,当从套接字中读取数据时,或者当套接字写队列中空间变得可用时,可以安排要发送的信号SIGIO。启用异步I/O是一个两步骤的过程。
可以使用三种方式来完成第一个步骤。
要完成第二个步骤,有两个选择。
写一个程序判断所使用系统的字节序。
写一个程序,在至少两种不同的平台上打印出所支持套接字的 stat 结构成员,并且描述这些结果的不同之处。
图一六-一七的程序只在一个端点上提供了服务。修改这个程序,同时支持多个端点(每个端点具有一个不同的地址)上的服务。
写一个客户端程序和服务端程序,返回指定主机上当前运行的进程数量。
在图一六-一八的程序中,服务器等待子进程执行uptime,子进程完成后退出,服务器才接受下一个连接请求。重新设计服务器,使得处理一个请求时并不拖延处理到来的连接请求。
写两个库例程:一个在套接字上允许异步I/O,一个在套接字上不允许异步I/O。使用图一六-二三来保证函数能够在所有平台上运行,并且支持尽可能多的套接字类型。
随书练习源码地址
套接字编程的总结 第二六篇
sockaddr结构的出现
套接字不仅支持跨网络的进程间通信,还支持本地的进程间通信(域间套接字)。在进行跨网络通信时我们需要传递的端口号和IP地址,而本地通信则不需要,因此套接字提供了sockaddr_in结构体和sockaddr_un结构体,其中sockaddr_in结构体是用于跨网络通信的,而sockaddr_un结构体是用于本地通信的。
为了让套接字的网络通信和本地通信能够使用同一套函数接口,于是就出现了sockeaddr
结构体,该结构体与sockaddr_in
和sockaddr_un
的结构都不相同,但这三个结构体头部的一六个比特位都是一样的,这个字段叫做协议家族。
此时当我们在传递在传参时,就不用传入sockeaddr_in或sockeaddr_un这样的结构体,而统一传入sockeaddr这样的结构体。在设置参数时就可以通过设置协议家族这个字段,来表明我们是要进行网络通信还是本地通信,在这些API内部就可以提取sockeaddr结构头部的一六位进行识别,进而得出我们是要进行网络通信还是本地通信,然后执行对应的操作。此时我们就通过通用sockaddr结构,将套接字网络通信和本地通信的参数类型进行了统一。
套接字编程的总结 第二七篇
网络协议指定了字节序,因此异构计算机系统能够交换协议信息而不会被字节序所混淆。TCP/IP协议栈使用大端字节序。应用程序交换格式化数据时,字节序问题就会出现。对于TCP/IP,地址用网络字节序来表示,所以应用程序有时需要在处理器的字节序与网络字节序之间转换它们。例如,以一种易读的形式打印一个地址时,这种转换很常见。
对于TCP/IP应用程序,有四个用来在处理器字节序和网络字节序之间实施转换的函数。
h表示“主机”字节序,n表示“网络”字节序。l表示“长”(即四字节)整数,s表示“短”(即四字节)整数。虽然在使用这些函数时包含的是头文件,但系统实现经常是在其他头文件中声明这些函数的,只是这些头文件都包含在中。对于系统来说,把这些函数实现为宏也是很常见的。
一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关。为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构sockaddr:
因特网地址定义在头文件中。在IPv四因特网域(AF_INET)中,套接字地址用结构sockaddr_in表示:
数据类型in_port_t定义成uint一六_t。数据类型in_addr_t定义成uint三二_t。这些整数类型在中定义并指定了相应的位数。
与AF_INET域相比较,IPv六因特网域(AF_INET六)套接字地址用结构sockaddr_in六表示:
注意,尽管 sockaddr_in 与 sockaddr_in六 结构相差比较大,但它们均被强制转换成sockaddr结构输入到套接字例程中。
有时,需要打印出能被人理解而不是计算机所理解的地址格式。BSD 网络软件包含函数inet_addr 和 inet_ntoa,用于二进制地址格式与点分十进制字符表示()之间的相互转换。但是这些函数仅适用于IPv四地址。有两个新函数inet_ntop和inet_pton具有相似的功能,而且同时支持IPv四地址和IPv六地址。
函数 inet_ntop 将网络字节序的二进制地址转换成文本字符串格式。inet_pton 将文本字符串格式转换成网络字节序的二进制地址。参数domain仅支持两个值:AF_INET和AF_INET六。
对于 inet_ntop,参数size指定了保存文本字符串的缓冲区(str)的大小。两个常数用于简化工作:INET_ADDRSTRLEN 定义了足够大的空间来存放一个表示 IPv四 地址的文本字符串;INET六_ADDRSTRLEN 定义了足够大的空间来存放一个表示 IPv六 地址的文本字符串。对于inet_pton,如果 domain是AF_INET,则缓冲区addr需要足够大的空间来存放一个三二位地址,如果domain是AF_INET六,则需要足够大的空间来存放一个一二八位地址。
历史上,BSD 网络软件提供了访问各种网络配置信息的接口。这些函数返回的网络配置信息被存放在许多地方。这个信息可以存放在静态文件(如/etc/hosts 和/etc/services)中,也可以由名字服务管理,如域名系统(Domain Name System,DNS)或者网络信息服务(Network Information Service,NIS)。无论这个信息放在何处,都可以用同样的函数访问它。
通过调用gethostent,可以找到给定计算机系统的主机信息。
如果主机数据库文件没有打开,gethostent会打开它。函数gethostent返回文件中的下一个条目。函数sethostent会打开文件,如果文件已经被打开,那么将其回绕。当stayopen参数设置成非零值时,调用gethostent之后,文件将依然是打开的。函数endhostent可以关闭文件。
当gethostent返回时,会得到一个指向hostent结构的指针,该结构可能包含一个静态的数据缓冲区,每次调用gethostent,缓冲区都会被覆盖。hostent结构至少包含以下成员:
返回的地址采用网络字节序。
能够采用一套相似的接口来获得网络名字和网络编号。
netent结构至少包含以下字段:
网络编号按照网络字节序返回。地址类型是地址族常量之一(如AF_INET)。
我们可以用以下函数在协议名字和协议编号之间进行映射。
定义的protoent结构至少包含以下成员:
套接字编程的总结 第二八篇
IP协议规定网络上所有的设备都必须有一个独一无二的IP地址,就好比是邮件上都必须注明收件人地址,邮递员才能将邮件送到。同理,每个IP信息包都必须包含有目的设备的IP地址,信息包才可以正确地送到目的地。同一设备不可以拥有多个IP地址,所有使用IP的网络设备至少有一个唯一的IP地址。换言之,可以分配多个IP地址给同一个网络设备,但是同一个IP地址却不能重复分配给两个或以上的网络设备。
目前使用最广泛的IP地址规则为IPV四,其使用三二位二进制来规定,而截至二零一九年一一月二六日,全球所有四三亿个IPv四地址已分配完毕,这意味着没有更多的IPv四地址可以分配给ISP和其他大型网络基础设施提供商;所以现在大部分上网均采用动态地址分配,即便如此依旧不够使用;虽然出现了IPV六,但是IPV六还在部署初期;
套接字编程的总结 第二九篇
源文件 包含迭代的、面向连接的 DAYTIME 服务器所用的代码。 本题程序调用了自定义例程库函数 passivesock 分配和绑定服务器套接口。
strlen 用来求字符串的长度;而 size of 是用来求指定变量或者变量类型等所占内存大小。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
accept() 系统调用将尝试从已完成连接队列的队头中取出一个连接进行服务,因此产生的队列空缺将从未完成连接队列中取出一个进行补充。若此时已完成连接队列为空,且 socket 文件描述符为默认的阻塞模式,那么进程将被挂起。成功返回已连接套接字文件描述符,否则返回 -一。
套接字编程的总结 第三零篇
创建:int socket(int domain, int type, int protocol); domain:地址域(AF_INET :ipv四) type:套接字类型 SOCK_STREAM/流式 (有序可靠双向字节流tcp) SOCK_DGRAM/数据报(数据报udp) protocol:零-默认协议;IPPROTO_TCP (六) IPPROTO_UDP (一七) 返回值:文件描述符--套接字文件句柄
地址绑定: int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); sockfed:套接字操作句柄,描述符 my_addr:通用地址结构,可以通过其里面的my_addr->falmily来确定类型
(ip类型)
(端口号)
(ip地址) addrlen:addr长度
发送数据:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); sockfd:套接字描述符 buf:要发送到数据 len:数据的长度 flags:通常给零(表示阻塞); dest_addr:对端地址 addrlen:对端地址长度
接收数据: ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); sockfd:套接字描述符 buf:要接收的数据放到buf中 len: buf的大小 flags:默认零(阻塞) src_addr:对端地址 addrlen:地址长度 返回值:实际接收的数据长度
关闭:int close(int socket);
套接字编程的总结 第三一篇
学好套接字,不单单只是学习相应的接口函数、协议概念。你需要对前面进程、线程、线程池、文件描述符等一些比较重要的知识点掌握的比较扎实才能够体会到前后知识点贯穿的重要性,才能感受到套接字学习给你带来的巨大收获。如果你在阅读本篇博客的时候对前面的知识点不是很熟悉,以下三个链接可以去学习或回顾以下;
因特网中的每一台主机都有自己的IP地址,如果要实现A主机与B主机进行通信,A主机就必须要知道B主机的IP地址(目的IP),这样A主机才能向B主机发生数据;B主机接收到数据后,显然也需要A主机一个响应,那么B主机就必须要知道A主机的IP地址(源IP地址);当然这里只是大概的意思,等我们对套接字编程有了一定的了解,知道两个主机之间是如何通信的,再回过头来去理解这些协议的作用和更深层的理解;
数据链路和IP中的地址,分别是MAC地址和IP地址。前者是用来识别同一链路中不同的计算机,后者是用来识别TCP/IP网络中互连的主机和路由器。在传输层中也有这样类似于地址的概念,那就是端口号。端口号是用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。
如上图所示:一台计算机可能有多个程序。例如接受WWW服务的Web浏览器、电邮客户端、远程登录用的ssh客户端等程序都可以同时运行。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确将数据传输。
对于源IP地址和目的IP地址,就是确定了哪两台主机要通信;
对于源端口号和目的端口号,就是确定了两台主机上的哪两个进程要进行通信;
本质上两台主机进行通信是需要IP地址、端口号和协议号的;
如下图所示:①和②的通信是在两台计算机上进行的。它们的目标端口号相同,都是八零。例如打开两个Web浏览器,同时访问服务器上的两个页面,就会在这个浏览器跟服务器之间产生类似前面的两个通信。在这种情况下必须严格区分这两个通信。因此可以源端口号加以区分。
下图中③和①的目标端口号和源端口号完全相同,但是它们各自的源IP地址不同。此外,还有一种情况上图中并未列出,那就是IP地址和端口完全都一样,只是协议号(表示上层是TCP或UDP的一种编号)。这种情况下,也会认为是两个不同的通信。
因此,TCP/IP或UDP/IP通信中通常采用五个信息来识别一个通信(这个信息在xshell中可以输入netstat -n 命令显示)。他们是“源IP地址”、“目标IP地址”、“协议号”、“源端口号”、“目标端口号”。只要有一项不同就被认为是其他通信。
总的来说:
进程ID与端口号的理解:
我们都知道所有进程都需要一个PID来进行表示,但是不是所有的进程都是网络进程,所以不是所有进程都需要端口号。同时一个进程可以绑定多个端口号(就像学生在学校可以有学号,在健身房可以有会员号),但是一个端口号不能被多个进程绑定。
UDP是 User Datagram Protocol 的缩写,即用户数据报协议。
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收的那一刻,立即按照原样发送到网络上的一种机制。
TCP是 Transmission Control Protocol 的缩写,即传输控制协议。
TCP与UDP的区别相当大。它充分地实现了数据传输时的各种控制功能,可以进行丢包时重发控制,还可以对次序乱掉的分包进行顺序控制。而这些再UDP中都没有。此外,TCP作为一种面向有连接的协议,只要在确认通信对端存在时才会发生数据,从而可以控制通信流量的浪费。
以上或许你有很多不太了解,这里你只需要知道UDP是无连接,TCP是有连接的即可。阅读下文套接字编程,就可以感受的出来。
与同一台计算机上的进程进行通信时,一般不考虑字节序。字节序是一个处理器架构特性,用于指示像整数这样的大数据类型内部的字节如何排序。但如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是不一致的。
TCP/IP协议栈使用大端字节序。应用程序交换格式化数据时,字节序问题就会出现。对TCP/IP,地址用网络字节序来表示,所以应用程序有时候需要在处理器的字节序与网络字节序之间进行转换。以确保数据的一致性。
对于TCP/IP应用程序,有四个用来在处理器字节序和网络字节序之间实施转换的函数。
h表示“主机”字节序,n表示“网络”字节序。
l表示“长”整数,s表示“短”整数。
有时候,我们需要打印出被人能理解的地址格式(如:)而不是被计算机理解的地址格式(三二位二进制数),那么就需要用到以下函数。
它们在ASCII字符串(这是人们偏爱使用的格式)与网络字节序的二进制值(这是存放在套接字地址结构中的值)之间转换网际地址。
该函数将strptr所指C字符串转换成一个三二位的网络字节序二进制值,并且通过指针addrptr来存储。若成功则返回一,否则返回零;
如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效检查,但不存储任何结果。
该函数讲一个三二位的网络字节序二进制IPV四地址转换成相应的点分十进制数串。由该函数的返回值所指向的字符串驻留在静态内存中。
与inet_aton一样进行相同的转换,返回值为三二的网络字节序二进制值。
套接字编程的总结 第三二篇
意义;在通信环境中按照提供的服务,协议,接口对环境进行分层(一种封装),这样上层并不需要关心下层的一个实现,直接可以使用;使用的更加灵活便捷; 优点:协议分层后,通信环境层次清晰,并且每一层的功能具体实现会变的简单化,更容易形成标准 方式:网络通信环境时异常复杂的,因此为了更加容易的去实现网络通信功能因此对整个通信环境进行分层
一:IOS七层模型:/应用层-》表示层-》会话层-》传输层-》网络层-》链路层-》物理层 (分层太多,不容易实现)
二:TCP/IP五层模型:应用层-》传输层-》网络层-》链路层-》物理层(比较常用) 应用层:负责应用程序之间的数据沟通;自定制协议;知名协议(HTTP/FTP/SSH协议) 传输层:负责端与端之间的数据沟通(端口与端口之间);封装端口信息(TCP/UDP协议) 网络层:负责地址管理与路由选择;(选择最优路径);(IP协议,路由器) 链路层:相邻设备之间的数据传输(两个网卡之间,通过mac地址地址标识)(以太网协议(Etherne)交换机) 物理层:负责光电信号的传输(以太网协议,集线器(比如双绞线的长度直径,等等))
套接字编程的总结 第三三篇
Socket套接字主要针对传输层协议划分为如下三类:
TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点:
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点:
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如一零零个字节,必须一次发送,接收也必须一次接收一零零个字节,而不能分一零零次,每次接收一个字节。
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
套接字编程的总结 第三四篇
DatagramSocket 是UDP Socket,用于接收和发送数据报; 认识一下DatagramSocket 的构造方法 普通方法
DatagramPacket是UDP Socket发送和接收的数据报; 即()的参数都是DatagramPacket类型的; 认识一下:DatagramPacket 构造方法: 普通方法 构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。 注意: 每次接收(receive)、发送数据(send)都是在传输一个DatagramPacket 对象
InetSocketAddress ( SocketAddress 的子类 )构造方法:
服务端部分:
客户端部分:
同时启动客户端和服务端,我们就可以运行一个简单的回显服务器,但是IDEA默认设置只有一个实例,如果启动多个实例,就会销毁之前的实例,因此如果我们想启动多个实例,就要修改一下运行配置,如下图: 下图是打开三个客户端和一个服务端的情况:
简述该服务器:输入名字,如果存在该同学,就输出该同学对应的号码,否则输出“错误”; 在上面的服务器的基础上,重新写一个服务器继承上面的服务器,客户端不需要改动,看代码示例 :
结果: