JAVA 通过 Socket 实现 TCP 编程
简介
TCP简介
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用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。
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,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
SocketImpl介绍
既然不管是Socket还是ServerSocket它们的工作都是通**过SocketImpl类及其子类完成的,那么当然要介绍啦。
抽象类 SocketImpl 是实际实现套接字的所有类的通用超类。创建客户端和服务器套接字都可以使用它。
具体JDK见:
http://www.javaweb.cc/help/JavaAPI1.6/index.html?java/nio/ReadOnlyBufferException.html
由于它是超类具体代码实现还是见下面的Socket
TCP 编程‘
构造ServerSocket
具体API见:http://www.javaweb.cc/help/JavaAPI1.6/index.html?java/nio/ReadOnlyBufferException.html
构造方法:
ServerSocket() ~创建非绑定服务器套接字。
ServerSocket(int port) ~创建绑定到特定端口的服务器套接字。
ServerSocket(int port, int backlog) ~利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。
ServerSocket(int port, int backlog, InetAddress bindAddr) ~使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
1.1 绑定端口
除了第一个不带参数的构造方法以外, 其他构造方法都会使服务器与特定端口绑定, 该端口有参数 port 指定. 例如, 以下代码创建了一个与 80 端口绑定的服务器:
ServerSocket serverSocket = new ServerSocket(80);
- 1
如果运行时无法绑定到 80 端口, 以上代码会抛出 IOException, 更确切地说, 是抛出 BindException, 它是 IOException 的子类. BindException 一般是由以下原因造成的:
- 端口已经被其他服务器进程占用;
- 在某些操作系统中, 如果没有以超级用户的身份来运行服务器程序, 那么操作系统不允许服务器绑定到 1-1023 之间的端口.
如果把参数 port 设为 0, 表示由操作系统来为服务器分配一个任意可用的端口. 有操作系统分配的端口也称为匿名端口. 对于多数服务器, 会使用明确的端口, 而不会使用匿名端口, 因为客户程序需要事先知道服务器的端口, 才能方便地访问服务器.
1.2 设定客户连接请求队列的长度
当服务器进程运行时, 可能会同时监听到多个客户的连接请求. 例如, 每当一个客户进程执行以下代码:
Socket socket = new Socket("www.javathinker.org", 80);
就意味着在远程 www.javathinker.org 主机的 80 端口上, 监听到了一个客户的连接请求. 管理客户连接请求的任务是由操作系统来完成的. 操作系统把这些连接请求存储在一个先进先出的队列中. 许多操作系统限定了队列的最大长度, 一般为 50 . 当队列中的连接请求达到了队列的最大容量时, 服务器进程所在的主机会拒绝新的连接请求. 只有当服务器进程通过 ServerSocket 的 accept() 方法从队列中取出连接请求, 使队列腾出空位时, 队列才能继续加入新的连接请求.
对于客户进程, 如果它发出的连接请求被加入到服务器的请求连接队列中, 就意味着客户与服务器的连接建立成功, 客户进程从 Socket 构造方法中正常返回. 如果客户进程发出的连接请求被服务器拒绝, Socket 构造方法就会抛出 ConnectionException.
Tips: 创建绑定端口的服务器进程后, 当客户进程的 Socket构造方法返回成功, 表示客户进程的连接请求被加入到服务器进程的请求连接队列中. 虽然客户端成功返回 Socket对象, 但是还没跟服务器进程形成一条通信线路. 必须在服务器进程通过 ServerSocket 的 accept() 方法从请求连接队列中取出连接请求, 并返回一个Socket 对象后, 服务器进程这个Socket 对象才与客户端的 Socket 对象形成一条通信线路.
ServerSocket 构造方法的 backlog 参数用来显式设置连接请求队列的长度, 它将覆盖操作系统限定的队列的最大长度. 值得注意的是, 在以下几种情况中, 仍然会采用操作系统限定的队列的最大长度:
- backlog 参数的值大于操作系统限定的队列的最大长度;
- backlog 参数的值小于或等于0;
在ServerSocket 构造方法中没有设置 backlog 参数.
以下的 Client.java 和 Server.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].close();
}
}
}
Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private int port = 1122;
private ServerSocket serverSocket; public Server() throws Exception{
serverSocket = new ServerSocket(port,3);
System.out.println("服务器启动!");
}
public void service(){
while(true){
Socket socket = null;
try {
socket = serverSocket.accept();
System.out.println("New connection accepted "+
socket.getInetAddress()+":"+socket.getPort());
} catch (IOException e) {
e.printStackTrace();
}finally{
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} public static void main(String[] args) throws Exception{
Server server = new Server();
Thread.sleep(60000*10);
server.service();
}
}
⑴ 在Server 中只创建一个 ServerSocket 对象, 在构造方法中指定监听的端口为1122 和 连接请求队列的长度为 3 . 构造 Server 对象后, Server 程序睡眠 10 分钟, 并且在 Server 中不执行 serverSocket.accept() 方法. 这意味着队列中的连接请求永远不会被取出. 运行Server 程序和 Client 程序后, Client程序的打印结果如下:
第 1 次连接成功
第 2 次连接成功
第 3 次连接成功
Exception in thread “main” java.net.ConnectException: Connection refused: connect
…………….
从以上打印的结果可以看出, Client 与 Server 在成功地建立了3 个连接后, 就无法再创建其余的连接了, 因为服务器的队已经满了.
⑵ 在Server中构造一个跟 ⑴ 相同的 ServerSocket对象, Server程序不睡眠, 在一个 while 循环中不断执行 serverSocket.accept()方法, 该方法从队列中取出连接请求, 使得队列能及时腾出空位, 以容纳新的连接请求. Client 程序的打印结果如下:
第 1 次连接成功
第 2 次连接成功
第 3 次连接成功
………..
第 100 次连接成功
从以上打印结果可以看出, 此时 Client 能顺利与 Server 建立 100 次连接.(每次while的循环要够快才行, 如果太慢, 从队列取连接请求的速度比放连接请求的速度慢的话, 不一定都能成功连接)
1.3 设定绑定的IP 地址
如果主机只有一个IP 地址, 那么默认情况下, 服务器程序就与该IP 地址绑定. ServerSocket 的第 4 个构造方法 ServerSocket(int port, int backlog, InetAddress bingAddr) 有一个 bindAddr 参数, 它显式指定服务器要绑定的IP 地址, 该构造方法适用于具有多个IP 地址的主机. 假定一个主机有两个网卡, 一个网卡用于连接到 Internet, IP为 222.67.5.94, 还有一个网卡用于连接到本地局域网, IP 地址为 192.168.3.4. 如果服务器仅仅被本地局域网中的客户访问, 那么可以按如下方式创建 ServerSocket:
ServerSocket serverSocket = new ServerSocket(8000, 10, InetAddress.getByName(“192.168.3.4”));
1.4 默认构造方法的作用
ServerSocket 有一个不带参数的默认构造方法. 通过该方法创建的 ServerSocket 不与任何端口绑定, 接下来还需要通过 bind() 方法与特定端口绑定.
这个默认构造方法的用途是, 允许服务器在绑定到特定端口之前, 先设置ServerSocket 的一些选项. 因为一旦服务器与特定端口绑定, 有些选项就不能再改变了.比如:SO_REUSEADDR 选项
在以下代码中, 先把 ServerSocket 的 SO_REUSEADDR 选项设为 true, 然后再把它与 8000 端口绑定:
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true); //设置 ServerSocket 的选项
serverSocket.bind(new InetSocketAddress(8000)); //与8000端口绑定
如果把以上程序代码改为:
ServerSocket serverSocket = new ServerSocket(8000);
serverSocket.setReuseAddress(true);//设置 ServerSocket 的选项
那么 serverSocket.setReuseAddress(true) 方法就不起任何作用了, 因为 SO_REUSEADDR 选项必须在服务器绑定端口之前设置才有效.
多线程示例
客户端:
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.close();
is.close();
pw.close();
os.close();
socket.close();
} 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.ServerSocket;
import java.net.Socket; /*
* 基于TCP协议的Socket通信,实现用户登陆
* 服务器端
*/
public class Server {
public static void main(String[] args) {
try {
//1.创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
ServerSocket serverSocket=new ServerSocket(8888);
Socket socket=null;
//记录客户端的数量
int count=0;
System.out.println("***服务器即将启动,等待客户端的连接***");
//循环监听等待客户端的连接
while(true){
//调用accept()方法开始监听,等待客户端的连接
socket=serverSocket.accept();
//创建一个新的线程
ServerThread serverThread=new ServerThread(socket);
//启动线程
serverThread.start(); count++;//统计客户端的数量
System.out.println("客户端的数量:"+count);
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 ServerThread extends Thread {
// 和本线程相关的Socket
Socket socket = null; public ServerThread(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.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JAVA 通过 Socket 实现 TCP 编程的更多相关文章
- 【Socket编程】通过Socket实现TCP编程
通过Socket实现TCP编程 Socket通信 : 1.TCP协议是面向对象连接.可靠的.有序的,以字节流的方式发送数据. 2.基于TCP协议实现网络通信的类: 客户端----Socket类 服务器 ...
- 通过Socket实现TCP编程,用户登录之服务器相应客户端,客户端和服务端之间的通信
服务器端: 1.创建ServerSocket对象,绑定监听端口: 2.通过accept()方法监听客户端请求: 3.建立连接后通过输入流读取客户端发送的请求信息; 4.通过输出流向客户端发送响应信息; ...
- 牛客网Java刷题知识点之TCP、UDP、TCP和UDP的区别、socket、TCP编程的客户端一般步骤、TCP编程的服务器端一般步骤、UDP编程的客户端一般步骤、UDP编程的服务器端一般步骤
福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号: 大数据躺过的坑 Java从入门到架构师 人工智能躺过的坑 Java全栈大联盟 ...
- Java的Socket通信----通过 Socket 实现 TCP 编程之多线程demo(2)
JAVA Socket简介 所谓socket 通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过”套接字”向网络发出请求或者应答网络请求. import java.io ...
- 【Linux/unix网络编程】之使用socket进行TCP编程
实验一 TCP数据发送与接收 [实验目的] 1.熟练掌握套接字函数的使用方法. 2.应用套接字函数完成基本TCP通讯,实现服务器与客户端的信息交互. [实验学时] 4学时 [实验内容] 实现一个服务器 ...
- python网络-Socket之TCP编程(26)
一.TCP简介 1.TCP介绍 TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的.可靠的.基于字节流的传输层通信协议. TC ...
- 通过Socket实现TCP编程(十二)
原文链接:https://www.cnblogs.com/hysum/p/7531529.html Socket通信 : TCP协议是面向对象连接.可靠的.有序的,以字节流的方式发送数据. 基于TCP ...
- 九、Socket之TCP编程
TCP简介 TCP是Transmission Control Protocol(传输控制协议)的简称,是TCP/IP体系中面向连接的运输层协议,在网络中提供全双工的和可靠的服务. TCP最主要的特点: ...
- 网络编程Socket之TCP
服务端: 1. 创建 ServerSocket 对象并监听一个端口 2. 调用accept()方法等待客户端的连接(阻塞式) 3. 输入流(记取客户端发送过来的数据) 4. 输出流(响 ...
随机推荐
- Grunt针对静态文件的压缩,版本控制打包方案
在讲之前先谈谈大致步骤:安装nodejs -> 全局安装grunt -> 项目创建package.json --> 项目安装grunt以及grunt插件 -> 配置Gruntf ...
- pmap 命令详解
通过查看帮助,返回了如下信息: Usage: pmap [options] pid [pid ...] Options: -x, --extended show detai ...
- Dom4J生成xml和包含CDATA问题
在 java注解生成xml和包含CDATA问题里面做了介绍,这里直接贴代码. 1:生成xml的java文件 package com.dufy.test.xml; import java.io.File ...
- DLL生成与使用的全过程
由dll导出的lib文件: 包含了每一个dll导出函数的符号名和可选择的标识号以及dll文件名,不含有实际的代码(这里的lib文件和静态库是不一样的),其中的导出导入函数都 是跳转指令,直接跳转到DL ...
- hive自定义UDF
udf udaf udtf 使用方式 hiverc文件 1.jar包放到安装日录下或者指定目录下 2.${HIVE_HOME}/bin目录下有个.hiverc文件,它是隐藏文件. 3.把初始化语句加载 ...
- bzoj4557【JLOI2016】侦查守卫
这道题对于我来说并不是特别简单,还可以. 更新一下blog 树形DP f[i][j]表示i的子树中,最高覆盖到i向下第j层的最小花费. g[i][j]表示i的子树全部覆盖,还能向上覆盖j层的最小花费. ...
- Python实战之set学习笔记及简单练习
['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__' ...
- http中错误代码的含义整理
HTTP网页错误代码大全带解释 HTTP 400 - 请求无效HTTP 401.1 - 未授权:登录失败HTTP 401.2 - 未授权:服务器配置问题导致登录失败HTTP 401.3 - ACL 禁 ...
- javaScript基础的基础
JavaScript是一个脚本语言,需要有宿主文件,他的宿主文件是HTML文件. 与JAVA没有直接关系 一般写在 1.head里面 2.body里面 3.</html>后面 一般写在&l ...
- 1、Http概述
1.1 Web客户端和服务器 HTTP 客户端和 HTTP 服务器共同构成了万维网的基本组件,客户端向服务器发送 HTTP 请求, 服务器会在 HTTP 响应中回送所请求的数据. 示意图: 1.2 媒 ...