tcp(transmission control protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流
的传输层通信协议,由ietf的rfc 793定义。在简化的计算机网络osi模型中,它完成第四层传输层
所指定的功能,用户数据报协议(udp,下一篇博客会实现)是同一层内 另一个重要的传输协议。在因特网协议族(internet protocol suite)中,tcp层是位于ip层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是ip层不提供这样的流机制,而是提供不可靠的包交换。
应用层向tcp层发送用于网间传输的、用8位字节表示的数据流,然后tcp把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( mtu)的限制)。之后tcp把结果包传给ip层,由它来通过网络将包传送给接收端实体的tcp层。tcp为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ack);如果发送端实体在合理的往返时延(rtt)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。tcp用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
所谓socket 通常也称作”套接字
“,用于描述ip地址和端口,是一个通信链的你在我心里句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求
。
以j2sdk-1.3为例,socket和rversocket类库位于java.net包中。rversocket用于服务器端,socket是建立网络连接时使用的
。在连接成功时,应用程序两端都会产生一个socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是socket还是rversocket它们的工作都是通过socketimpl类及其子类完成
的。
重要的socket api:
java.net.socket继承于java.lang.object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见jdk-1.3文档。
. accept方法用于产生”阻塞”,直到接受到一个连接,并且返回一个客户端的socket对象实例。”阻塞”是一个术语,它使程序运行暂时”停留”在这个地方,直到一个会话产生,然后程序继续;通常”阻塞”是由循环产生的。. getinputstream方法获得网络连接输入,同时返回一个inputstream对象实例。. getoutputstream方法连接的另一端将得到输入,同时返回一个outputstream对象实例。注意:其中getinputstream和getoutputstream方法均会产生一个ioexception,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
既然不管是socket还是rversocket它们的工作都是通过socketimpl类及其子类完成的,那么当然要介绍啦。
抽象类 socketimpl 是实际实现套接字的所有类的思想汇报2000字通用超类。创建客户端和服务器套接字都可以使用它。
具体jdk见:
由于它是超类具体代码实现还是见下面的socket
具体api见:
构造方法:
rversocket() ~创建非绑定服务器套接字。rversocket(int port) ~创建绑定到特定端口的服务器套接字。rversocket(int port, int backlog) ~利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。rversocket(int port, int backlog, inetaddress bindaddr) ~使用指定的端口、侦听 backlog 和要绑定到的本地 ip 地址创建服务器。除了第一个不带参数的构造方法以外, 其他构造方法都会使服务器与特定端口绑定, 该端口有参数 port 指定. 例如, 以下代码创建了一个与 80 端口绑定的服务器:
rversocket rversocket = new rversocket(80);
如果运行时无法绑定到 80 端口, 以上代码会抛出 ioexception, 更确切地说, 是抛出 bindexception, 它是 ioexception 的子类. bindexception 一般是由以下原因造成的:
端口已经被其他服务器进程占用;在某些操作系统中, 如果没有以超级用户的身份来运行服务器程序, 那么操作系统不允许服务器绑定到 1-1023 之间的端口.如果把参数 port 设为 0, 表示由操作系统来为服务器分配一个任意可用的端口. 有操作系统分配的端口也称为匿名端口. 对于多数服务器, 会使用明确的端口, 而不会使用匿名端口, 因为客户程序需要事先知道服务器的端口, 才能方便地访问服务器.
当服务器进程运行时, 可能会同时监听到多个客户的连接请求. 例如, 每当一个客户进程执行以下代码:
socket socket = new socket("www.javathinker.org", 80);
就意味着在远程 www.javathinker.org 主机的 80 端口上, 监听到了一个客户的连接请求. 管理客户连接请求的任务是由操作系统来完成的. 操作系统把这些连接请求存储在一个先进先出的队列中. 许多操作系统限定了队列的最大长度, 一般为 50 . 当队列中的连接请求达到了队列的最大容量时, 服务器进程所在的主机会拒绝新的连接请求. 只有当服务器进程通过 rversocket 的 accept() 方法从队列中取出连接请求, 使队列腾出空位时, 队列才能继续加入新的连接请求.
对于客户进程, 如果它发出的连接请求被加入到服务器的请求连接队列中, 就意味着客户与服务器的连接建立成功, 客户进程从 socket 构造方法中正常返回. 如果客户进程发出的连接请求被服务器拒绝, socket 构造方法就会抛出 connectionexception.
tips: 创建绑定端口的服务器进程后, 当客户进程的 socket构造方法返回成功, 表示客户进程的连接请求被加入到服务器进程的请求连接队列中. 虽然客户端成功返回 socket对象, 但是还没跟服务器进程形成一条通信线路. 必须在服务器进程通过 rversocket 的 accept() 方法从请求连接队列中取出连接请求, 并返回一个socket 对象后, 服务器进程这个socket 对象才与客户端的 socket 对象形成一条通信线路.
rversocket 构造方法的 backlog 参数用来显式设置连接请求队列的长度, 它将覆盖操作系统限定的队列的最大长度. 值得注意的是, 在以下几种情况中, 仍然会采用操作系统限定的队列的最大长度:
backlog 参数的值大于操作系统限定的队列的最大长度;backlog 参数的值小于或等于0;在rversocket 构造方法中没有设置 backlog 参数.以下的 client.java 和 rver.java 用来演示服务器的连接请求队列的特性.
client.java
import java.net.socket;public class client { public static void main(string[] args) throws exception{ final int length = 100; string host = "localhost"; int port = 1122; socket[] socket = new socket[length]; for(int i = 0;i<length;i++){ socket[i] = new socket(host,port); system.out.println("第"+(i+1)+"次连接成功!"); } thread.sleep(3000); for(int i=0;i<length;i++){ socket[i].clo(); } }}
rver.java
import java.io.ioexception;import java.net.rversocket;import java.net.socket;public class rver { private int port = 1122; private rversocket rversocket; public rver() throws exception{ rversocket = new rversocket(port,3); system.out.println("服务器启动!"); } public void rvice(){ while(true){ socket socket = null; try { socket = rversocket.accept(); system.out.println("new connection accepted "+ socket.getinetaddress()+":"+socket.getport()); } catch (ioexception e) { e.printstacktrace(); }finally{ if(socket!=null){ try { socket.clo(); } catch (ioexception e) { e.printstacktrace(); } } } } } public static void main(string[] args) throws exception{ rver rver = new rver(); thread.sleep(60000*10); rver.rvice(); }}
client 试图与 rver 进行 100 次连接. 在 rver 类中, 把连接请求队列的长度设为 3. 这意味着当队列中有了 3 个连接请求时, 如果client 再请求连接, 就会被 rver 拒绝. 下面按照以下步骤运行 rver 和 client 程序.
⑴ 在rver 中只创建一个 rversocket 对象, 在构造方法中指定监听的端口为1122 和 连接请求队列的长度为 3 . 构造 rver 对象后, rver 程序睡眠 10 分钟, 并且在 rver 中不执行 rversocket.accept() 方法. 这意味着队列中的连接请求永远不会被取出. 运行rver 程序和 client 程序后, client程序的打印结果如下: 第 1 次连接成功 第 2 次连接成功 第 3 次连接成功 exception in thread “main” java.net.connectexception: connection refud: connect ……………. 从以上打印的结果可以看出, client 与 rver 在成功地建立了3 个连接后, 就无法再创建其余的连接了, 因为服务器的队已经满了. ⑵ 在rver中构造一个跟 ⑴ 相同的 rversocket对象, rver程序不睡眠, 在一个 while 循环中不断执行 rversocket.accept()方法, 该方法从队列中取出连接请求, 使得队列能及时腾出空位, 以容纳新的连接请求. client 程序的打印结果如下: 第 1 次连接成功 第 2 次连接成功 第 3 次连接成功 ……….. 第 100 次连接成功 从以上打印结果可以看出, 此时 client 能顺利与 rver 建立 100 次连接.(每次while的循环要够快才行, 如果太慢, 从队列取连接请求的速度比放连接请求的速度慢的话, 不一定都能成功连接)
如果主机只有一个ip 地址, 那么默认情况下, 服务器程序就与该ip 地址绑定. rversocket 的第 4 个构造方法 rversocket(int port, int backlog, inetaddress bingaddr) 有一个 bindaddr 参数, 它显式指定服务器要绑定的ip 地址, 该构造方法适用于具有多个ip 地址的主机. 假定一个主机有两个网卡, 一个网卡用于连接到 internet, ip为 222.67.5.94, 还有一个网卡用于连接到本地局域网, ip 地址为 192.168.3.4. 如果服务器仅仅被本地局域网中的客户访问, 那么可以按如下方式创建 rversocket:
rversocket rversocket = new rversocket(8000, 10, inetaddress.getbyname(“192.168.3.4”));
rversocket 有一个不带参数的默认构造方法. 通过该方法创建的 rversocket 不与任何端口绑定, 接下来还需要通过 bind() 方法与特定端口绑定.
这个默认构造方法的用途是, 允许服务器在绑定到特定端口之前, 先设置rversocket 的一些选项. 因为一旦服务器与特定端口绑定, 有些选项就不能再改变了.比如:so_reuaddr 选项
在以下代码中, 先把 rversocket 的 so_reuaddr 选项设为 true, 然后再把它与 8000 端口绑定:
rversocket rversocket = new rversocket();rversocket.treuaddress(true); //设置 rversocket 的选项rversocket.bind(new inetsocketaddress(8000)); //与8000端口绑定
如果把以上程序代码改为:
rversocket rversocket = new rversocket(8000);rversocket.treuaddress(true);//设置 rversocket 的选项
那么 rversocket.treuaddress(true) 方法就不起任何作用了, 因为 so_reuaddr 选项必须在服务器绑定端口之形容老师的成语前设置才有效.
客户端:
import java.io.bufferedreader;import java.io.ioexception;import java.io.inputstream;import java.io.inputstreamreader;import java.io.outputstream;import java.io.printwriter;import java.net.socket;import java.net.unknownhostexception;/* * 客户端 */public class client { public static void main(string[] args) { try { //1.创建客户端socket,指定服务器地址和端口 socket socket=new socket("localhost", 8888); //2.获取输出流,向服务器端发送信息 outputstream os=socket.getoutputstream();//字节输出流 printwriter pw=new printwriter(os);//将输出流包装为打印流 pw.write("用户名:whf;密码:789"); pw.flush(); socket.shutdownoutput();//关闭输出流 //3.获取输入流,并读取服务器端的响应信息 inputstream is=socket.getinputstream(); bufferedreader br=new bufferedreader(new inputstreamreader(is)); string info=null; while((info=br.readline())!=null){ system.out.println("我是客户端,服务器说:"+info); } //4.关闭资源 br.clo(); is.clo(); pw.clo(); os.clo(); socket.clo(); } catch (unknownhostexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } }}
服务器:
import java.io.bufferedreader;import java.io.ioexception;import java.io.inputstream;import java.io.inputstreamreader;import java.io.outputstream;import java.io.printwriter;import java.net.inetaddress;import java.net.rversocket;import java.net.socket;/* * 基于tcp协议的socket通信,实现用户登陆 * 服务器端 */public class rver { public static void main(string[] args) { try { //1.创建一个服务器端socket,即rversocket,指定绑定的端口,并监听此端口 rversocket rversocket=new rversocket(8888); socket socket=null; //记录客户端的数量 int count=0; system.out.println("***服务器即将启动,等待客户端的连接***"); //循环监听等待客户端的连接 while(true){ //调用accept()方法开始监听,等待客户端的连接 socket=rversocket.accept(); //创建一个新的线程 rverthread rverthread=new rverthread(socket); //启动线程 rverthread.start(); count++;//统计客户端的数量 system.out.println("客户端的数量:"+co第二军医大unt); inetaddress address=socket.getinetaddress(); system.out.println("当前客户端的ip:"+address.gethostaddress()); } } catch (ioexception e) { e.printstacktrace(); } }}
服务器处理类:
import java.io.bufferedreader;import java.io.ioexception;import java.io.inputstream;import java.io.inputstreamreader;import java.io.outputstream;import java.io.printwriter;import java.net.socket;/* * 服务器线程处理类 */public class rverthread extends thread { // 和本线程相关的socket socket socket = null; public rverthread(socket socket) { this.socket = socket; } //线程执行的操作,响应客户端的请求 public void run(){ inputstream is=null; inputstreamreader isr=null; bufferedreader br=null; outputstream os=null; printwriter pw=null; try { //获取输入流,并读取客户端信息 is = socket.getinputstream(); isr = new inputstreamreader(is); br = new bufferedreader(isr); string info=null; while((info=br.readline())!=null){//循环读取客户端的信息 system.out.println("我是服务器,客户端说:"+info); } socket.shutdowninput();//关闭输入流 //获取输出流,响应客户端的请求 os = socket.getoutputstream(); pw = new printwriter(os); pw.write("欢迎您!"); pw.flush();//调用flush()方法将缓冲输出 } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); }finally{ //关闭资源 try { if(pw!=null) pw.clo(); if(os!=null) os.clo(); if(br!=null) br.clo(); 哀鸿遍野造句 if(isr!=null) isr.clo(); if(is!=null) is.clo(); if(socket!=null) socket.clo(); } catch (ioexception e) { e.printstacktrace(); } } }}
以上所述是www.887551.com给大家介绍的java基于tcp协议的socket通信,希望对大家有所帮助。在此也非常感谢大家对www.887551.com网站的支持!
本文发布于:2023-04-04 03:30:30,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/1eca526ac67e67230e8c035556f63aba.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:Java基于TCP协议的Socket通信.doc
本文 PDF 下载地址:Java基于TCP协议的Socket通信.pdf
留言与评论(共有 0 条评论) |