A. java中socket填的ip
首先必须明确:TCP/IP模型中有四层结构:
应用层(Application Layer)、传输层(Transport Layer)、网络层(Internet Layer )、链路层(LinkLayer)
其中Ip协议(Internet Protocol)是位于网络层的,TCP协议时位于传输层的。通过Ip协议可以使可以使两台计算机使用同一种语言,从而允许Internet上连接不同类型的计算机和不同操作系统的网络。Ip协议只保证计算机能够接收和发送分组数据。 当计算机要和远程的计算机建立连接时,TCP协议会让他们建立连接:用于发送和接收数据的虚拟电路。
在JAVA中,我们用 ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。
套接字或插座(socket)是一种软件形 式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。JAVA 有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;Socket,客户端用它初始一次连接。侦听套接字只能接收新的 连接请求,不能接收实际的数据包,即ServerSocket不能接收实际的数据包。
套接字是基于TCP/IP实现的,它是用来提供一个访问TCP的服务接口,或者说套接字socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。
在JAVA中,我们用 ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便 将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。
套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的。下面先讨论套接字读写缓冲区,接着讨论连接建立超时、读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示例。
1 socket读写缓冲区
一旦创建了一个套接字实例,操作系统就会为其分配缓冲区以存放接收和要发送的数据。
JAVA可以设置读写缓冲区的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。
向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲区队列SendQ,就是在Socket的OutputStream上调用 flush()方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。
当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。
2 socket连接建立超时
socket连接建立是基于TCP的连接建立过程。TCP的连接需要通过3次握手报文来完成,开始建立TCP连接时需要发送同步SYN报文,然后等待确认 报文SYN+ACK,最后再发送确认报文ACK。TCP连接的关闭通过4次挥手来完成,主动关闭TCP连接的一方发送FIN报文,等待对方的确认报文;被 动关闭的一方也发送FIN报文,然等待确认报文。
正在等待TCP连接请求的一端有一个固定长度的连接队列,该队列中的连接已经被TCP接受(即三次握手已经完成),但还没有被应用层所接受。TCP接受一个连接是将其放入这个连接队列,而应用层接受连接是将其从该队列中移出。应用层可以通过设置backlog变量来指明该连接队列的最大长度,即已被TCP接受而等待应用层接受的最大连接数。
当一个连接请求SYN到达时,TCP确定是否接受这个连接。如果队列中还有空间,TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在三次握手中的第三个报文收到后才会知道这个新连接。如果队列没有空间,TCP将不理会收到的SYN。
如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,新的连接请求可能不被响应而会超时。如果一个连接请求SYN发送后,一段时间后没有收到确认SYN+ACK,TCP会重传这个连接请求SYN两次,每次重传的时间间隔加倍,在规定的时间内仍没有收到SYN+ACK,TCP将放弃这个连接请求,连接建立就超时了。
JAVA Socket连接建立超时和TCP是相同的,如果TCP建立连接时三次握手超时,那么导致Socket连接建立也就超时了。可以设置Socket连接建立的超时时间-
connect(SocketAddress endpoint, int timeout)
如果在timeout内,连接没有建立成功,在TimeoutException异常被抛出。如果timeout的值小于三次握手的时间,那么Socket连接永远也不会建立。
不同的应用层有不同的连接建立过程,Socket的连接建立和TCP一样-仅仅需要三次握手就完成连接,但有些应用程序需要交互很多信息后才能成功建立连接,比如Telnet协议,在TCP三次握手完成后,需要进行选项协商之后,Telnet连接才建立完成。
3 socket读超时
如果输入缓冲队列RecvQ中没有数据,read操作会一直阻塞而挂起线程,直到有新的数据到来或者有异常产生。调用setSoTimeout(int timeout)可以设置超时时间,如果到了超时时间仍没有数据,read会抛出一个SocketTimeoutException,程序需要捕获这个异 常,但是当前的socket连接仍然是有效的。
如果对方进程崩溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去(由前面可知:双方要关闭连接需要四次挥手 .对方机重启或断开只是对方机的TCP连接关闭,本端的TCP连接还没关闭,所以本端机会一直阻塞),这时设置超时时间是非常重要的,否则调用read的线程会一直挂起。
TCP模块把接收到的数据放入RecvQ中,直到应用层调用输入流的read方法来读取。如果RecvQ队列被填满了,这时TCP会根据滑动窗口机制通知 对方不要继续发送数据,本端停止接收从对端发送来的数据,直到接收者应用程序调用输入流的read方法后腾出了空间。
4 socket写超时
socket的写超时是基于TCP的超时重传。超时重传是TCP保证数据可靠性传输的一个重要机制,其原理是在发送一个数据报文后就开启一个计时器,在一 定时间内如果没有得到发送报文的确认ACK,那么就重新发送报文。如果重新发送多次之后,仍没有确认报文,就发送一个复位报文RST,然后关闭TCP连 接。首次数据报文发送与复位报文传输之间的时间差大约为9分钟,也就是说如果9分钟内没有得到确认报文,就关闭连接。但是这个值是根据不同的TCP协议栈 实现而不同。
如果发送端调用write持续地写出数据,直到SendQ队列被填满。如果在SendQ队列已满时调用write方法,则write将被阻塞,直到 SendQ有新的空闲空间为止,也就是说直到一些字节传输到了接收者套接字的RecvQ中。如果此时RecvQ队列也已经被填满,所有操作都将停止,直到 接收端调用read方法将一些字节传输到应用程序。
当Socket的write发送数据时,如果网线断开、对端进程崩溃或者对端机器重启动,(由前面可知:双方要关闭连接需要四次挥手 .对端进程崩溃或者对端机器重启动只是对方机的TCP连接关闭,本端的TCP连接还没关闭,所以本端机会一直阻塞)TCP模块会重传数据,最后超时而关闭连接。下次如再调用write会导致一个异常而退出。
Socket写超时是基于TCP协议栈的超时重传机制,一般不需要设置write的超时时间,也没有提供这种方法。
5 双重嵌套异常捕获
如果ServerSocket、Socket构造失败,只需要仅仅捕获这个构造失败异常而不需要调用套接字的close方法来释放资源(必须保证构造失败 后不会留下任何需要清除的资源),因为这时套接字内部资源没有被成功分配。如果构造成功,必须进入一个try finally语句块里调用close释放套接字。请参照下面例子程序。
import java.net.*;
import java.io.*;
public class SocketClientTest
{
public static final int PORT = 8088;
public static void main( String[] args ) throws Exception
{
InetAddress addr = InetAddress.getByName( "127.0.0.1" );
Socket socket = new Socket();
try
{
socket.connect( new InetSocketAddress( addr, PORT ), 30000 );
socket.setSendBufferSize(100);
BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
int i = 0;
while( true )
{
System.out.println( "client sent --- hello *** " + i++ );
out.write( "client sent --- hello *** " + i );
out.flush();
Thread.sleep( 1000 );
}
}
finally
{
socket.close();
}
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerTest
{
public static final int PORT = 8088;
public static final int BACKLOG = 2;
public static void main( String[] args ) throws IOException
{
ServerSocket server = new ServerSocket( PORT, BACKLOG );
System.out.println("started: " + server);
try
{
Socket socket = server.accept();
try
{
BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
String info = null;
while( ( info = in.readLine() ) != null )
{
System.out.println( info );
}
}
finally
{
socket.close();
}
}
finally
{
server.close();
}
}
}
B. java程序中使用tcp套接字编写服务端程序的套接字类是
ServerSocket。
“java程序中使用tcp套接字编写服务端程序的套接字类是”是《Java语言》复习资料的一道练习题,答案是ServerSocket。
Java是一门编程语言,是一门连接人与计算机的语言。
C. java socket有什么作用
所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。
以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
重要的Socket API:
java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。
. Accept方法用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。"阻塞"是一个术语,它使程序运行暂时"停留"在这个地方,直到一个会话产生,然后程序继续;通常"阻塞"是由循环产生的。
. getInputStream方法获得网络连接输入,同时返回一个InputStream对象实例,。
. getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。
注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
编辑本段ServerSocket类例子
int PORT = 8888; // 侦听端口
// 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(PORT);
// 开始循环
while (true) {
// 等待连接
Socket socket = serverSocket.accept();
// 处理链接的线程类
ServerThread st = new ServerThread(socket);
// 启动线程处理
new Thread(st).start();
}
编辑本段客户端的例子
int PORT = 8888; // 侦听端口
// 建立连接
socket = new Socket(“127.0.0.1”, 8888);
// 输入数据的读取
BufferedReader netIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 写入数据
PrintWriter netOut = new PrintWriter(socket.getOutputStream());
D. 在javasocket网络编程中,开发基于udp协议的程序使用的套接字有哪些
Socket套接字,是由系统提供用于网络通信的技术(操作系统给应用程序提供的一组API叫做Socket API),是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
socket可以视为是应用层和传输层之间的通信桥梁;
传输层的核心协议有两种:TCP,UDP;socket API也有对应的两组,由于TCP和UDP协议差别很大,因此,这两组API差别也挺大。
分类:
Socket套接字主要针对传输层协议划分为如下三类:
流套接字:使用传输层TCP协议
TCP,即Transmission Control Protocol(传输控制协议),传输层协议;
TCP的特点:
有连接:像打电话,得先接通,才能交互数据;
可靠传输:传输过程中,发送方知道接收方有没有收到数据.(打电话就是可靠传输);
面向字节流:以字节为单位进行传输.(非常类似于文件操作中的字节流);
全双工:一条链路,双向通信;
有接收缓冲区,也有发送缓冲区。
大小不限
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。
数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
UDP的特点:
无连接:像发微信,不需要接通,直接就能发数据;
不可靠传输:传输过程中,发送方不知道接收方有没有收到数据.(发微信就是不可靠传输);
面向数据报:以数据报为单位进行传输(一个数据报都会明确大小)一次发送/接收必须是一个完整的数据报,不能是半个,也不能是一个半;
全双工:一条链路,双向通信;
有接收缓冲区,无发送缓冲区;
大小受限:一次最多传输64k;
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。
原始套接字
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
二、UDP数据报套接字编程
UDPSocket中,主要涉及到两类:DatagramSocket、DatagramPacket;
DatagramSocket API
DatagramSocket 创建了一个UDP版本的Socket对象,用于发送和接收UDP数据报,代表着操作系统中的一个socket文件,(操作系统实现的功能–>)代表着网卡硬件设备的抽象体现。
DatagramSocket 构造方法:
方法签名 方法说明
DatagramSocket() 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port) 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
DatagramSocket 方法:
方法签名 方法说明
void receive(DatagramPacket p) 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p) 从此套接字发送数据报包(不会阻塞等待,直接发送)
void close() 关闭此数据报套接字
DatagramPacket API
代表了一个UDP数据报,是UDP Socket发送和接收的数据报,每次发送/接收数据报,都是在传输一个DatagramPacket对象。
DatagramPacket 构造方法:
方法签名 方法说明
DatagramPacket(byte[] buf, int length) 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int length,SocketAddress address) 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号
DatagramPacket 方法:
方法签名 方法说明
InetAddress getAddress() 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort() 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData() 获取数据报中的数据
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
方法签名 方法说明
InetSocketAddress(InetAddress addr, int port) 创建一个Socket地址,包含IP地址和端口号
示例1:写一个简单的客户端服务程序,回显服务(EchoSever)
在这里插入图片描述
构建Socket对象有很多失败的可能:
端口号已经被占用,同一个主机的两个程序不能有相同的端口号(这就好比两个人不能拥有相同的电话号码);
此处,多个进程不能绑定同一个端口号,但是一个进程可以绑定多个端口,(这就好比一个人可以拥有多个手机号),一个进程可以创建多个Socket对象,每个Socket都绑定自己的端口。
每个进程能够打开的文件个数是有上限的,如果进程之间已经打开了很多文件,就可能导致此时的Socket文件不能顺利打开;
在这里插入图片描述
这个长度不一定是1024,假设这里的UDP数据最长是1024,实际的数据可能不够1024.
在这里插入图片描述
这里的参数不再是一个空的字节数组了,response是刚才根据请求计算的得到的响应,是非空的,DatagramPacket 里面的数据就是String response的数据。
response.getBytes().length:这里拿到的是字节数组的长度(字节的个数),而response.length得到的是字符的长度。
五元组
一次通信是由5个核心信息描述的:源IP、 源端口、 目的IP、 目的端口、 协议类型。
站在客户端角度:
源IP:本机IP;
源端口:系统分配的端口;
目的IP:服务器的IP;
目的端口:服务器的端口;
协议类型:TCP;
站在服务器的角度:
源IP:服务器程序本机的IP;
源端口:服务器绑定的端口(此处手动指定了9090);
目的IP:包含在收到的数据报中(客户端的IP);
目的端口:包含在收到的数据报中(客户端的端口);
协议类型:UDP;