摘要:进行 TCP 协定网络程序的编写,关键在于 ServerSocket 套接字的纯熟应用,TCP 通信中所有的信息传输都是依靠 ServerSocket 类的输入输出流进行的。
本文分享自华为云社区《Java 利用 TCP 协定实现客户端与服务器通信【附通信源码】》,作者:灰小猿。
TCP 协定概念
咱们晓得 TCP 是牢靠而非平安的网络协议。它能够保证数据在从一端送至另一端的时候能够精确的送达,并且到达的数据的排列程序和送出时的程序是雷同的。 因而在进行 TCP 协定通信的时候,咱们首先应该保障客户端和服务器之间的连贯通顺。
而 TCP 协定程序的编写,依然是依附套接字 Socket 类来实现的,并且利用 TCP 协定进行通信的两个程序之间是有主次之分的,即一个是服务器的程序,另一个是客户端的程序。因而两者的性能和编写上也略有不同。如下图是服务器与客户端之间进行通信的示意图:
以上就是在 TCP 协定中客户端与服务器建设连贯的过程示意图。而在这其中起到关键作用的就是服务器端套接字 ServerSocket 和客户端套接字 Socket。通过这两个套接字来建设服务器和客户端,从而利用其中的函数进行数据的通信。
在 ServerSocket 类中有很多须要留神的中央,接下来大灰狼和大家分享一下 ServerSocket 类的具体用法:
ServerSocket 类
ServerSocket 类存在于 http://Java.net 包中,示意服务器端的套接字,在应用时须要首先导入这个类,咱们也晓得 ServerSocket 类的次要性能就是通过指定的端口期待来自于网络中客户端的申请并且进行连贯。
值得注意的是:服务器套接字一次只能与一个客户端套接字进行连贯,因而如果存在多台客户端同时发送连贯申请,则服务器套接字就会将申请的客户端寄存到队列中去,而后从中取出一个套接字与服务器建设的套接字进行连贯,然而服务器端可能包容的客户端套接字也不是有限的,当申请连贯的数量大于最大包容量时,那么多进去的申请就会被拒接,一般来说队列的默认大小是 50。
ServerSocket 类的构造方法通常会抛出 IOException 异样,具体有以下几种模式:
• ServerSocket():创立非绑定服务器套接字
• ServerSocket(inr port):创立绑定到特定端口的服务器套接字
• ServerSocket(int port, int backlog):利用指定的 backlog 创立服务器套接字,并将其绑定到指定的服务器端口上,
• ServerSocket(int port, int backlog, InetAddress bindAddress):应用指定的端口,侦听 backlog 和要绑定到本地的 IP 地址创立服务器。这种状况实用于计算机上有多个网卡和多个 IP 地址的状况,用户能够明确的规定 ServerSocket 在哪块网卡或哪个 IP 地址上期待用户的连贯申请。
以下是 ServerSocket 类中一些罕用的办法:
理解了 ServerSocket 类的根本办法之后,就是如何进行客户端和服务器进行连贯的问题了。
在服务器端咱们能够调用 ServerSocket 类的 accpet()办法与申请连贯的客户机建设连贯,这时会返回一个和客户端相连接的 Socket 对象,这个时候其实曾经连贯胜利了,应用 getInetAddress()办法就能够获取到进行申请的客户机的 IP 地址。
对于如何进行客户端和服务器端数据的通信,就要用到数据的输出流和输入流了,服务器端的 Socket 对象应用 getOutputStream()办法获取到的输入流,将指向客户端的 Socket 对象应用 getInputStream()办法获取到的输出流。由此就实现在服务器向客户端发送数据的一个过程,同样的情理,客户端端的 Socket 对象应用 getOutputStream()办法获取到的输入流,将指向服务器端的 Socket 对象应用 getInputStream()办法获取到的输出流。从而实现由客户端向服务器发送数据的过程。
留神:accpet()办法会阻塞线程的继续执行,如果在对应的接口没有收到客户端的呼叫,则程序会停留在此处,直到获取到客户端的呼叫才会持续向下执行,然而如果服务器没有收到来自客户端的呼叫申请,并且 accpet()办法没有产生阻塞,那么通常状况下就是程序出了问题,一般来说可能是应用了一个曾经被其余程序占用了的端口号,导致 ServerSocket 没有绑定胜利!遇到这种状况能够尝试更换新的端口号。
理解了 TCP 协定的通信过程,接下来就是进行 TCP 通信程序的书写啦!
在网络通信中,如果只要求客户机向服务器发送信息,不要求服务器向客户端反馈信息的行为称为“单向通信”,要求客户机和服务器单方相互通信的过程称为“双向通信”,双向通信只不过是比单向通信多了一个服务器向客户端发送音讯的过程,
接下来别离是服务器端和客户端程序的编写:
服务器端程序
package server_1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTcp {
private ServerSocket server; // 设置服务器套接字
private Socket client; // 设置客户端套接字
// 连贯客户端函数
void getServer()
{
try {server = new ServerSocket(1100); // 建设服务器 端口为 1100
System.out.println("服务器建设胜利!正在期待连贯......");
client = server.accept(); // 调用服务器函数对客户端进行连贯
System.out.println("客户端连贯胜利!ip 为:" + client.getInetAddress()); // 返回客户端 IP
getClientMessage(); // 调用信息传输和接管函数} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
}
void getClientMessage()
{
try {while (true) {InputStream is = client.getInputStream(); // 获取到客户端的输出流
byte[] b = new byte[1024]; // 定义字节数组
int len = is.read(b); // 因为信息的传输是以二进制的模式,所以要以二进制的模式进行数据的读取
String data = new String(b, 0,len);
System.out.println("客户端发来音讯:" + data);
// 定义发送给客户端的输入流
OutputStream put = client.getOutputStream();
String putText = "我曾经收到!欢送你!";
put.write(putText.getBytes()); // 将输入流信息以二进制的模式进行写入
}
} catch (Exception e) {// TODO: handle exception}
try {
// 判断客户端字节流不是空,则敞开客户端
if (server != null) {server.close();
}
} catch (Exception e) {// TODO: handle exception}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MyTcp myTcp = new MyTcp(); // 调用该类生成对象
myTcp.getServer(); // 调用办法}
}
客户端程序
package client_1;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClient {
private Socket client; // 定义客户端套接字
// 建设客户端函数
void getClient()
{
try {client = new Socket("127.0.0.1", 1100); // 建设客户端,应用的 IP 为 127.0.0.1,端口和服务器一样为 1100
System.out.println("客户端建设胜利!");
setClientMessage(); // 调用客户端信息写入函数} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
}
// 定义客户端信息写入函数
void setClientMessage()
{
try {OutputStream pt = client.getOutputStream(); // 建设客户端信息输入流
String printText = "服务器你好!我是客户端!";
pt.write(printText.getBytes()); // 以二进制的模式将信息进行输入
InputStream input = client.getInputStream(); // 建设客户端信息输出流
byte [] b = new byte[1024]; // 定义字节数组
int len = input.read(b); // 读取接管的二进制信息流
String data = new String(b, 0,len);
System.out.println("收到服务器音讯:" + data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
try {
// 如果客户端信息流不为空,则阐明客户端曾经建设连贯,敞开客户端
if (client != null) {client.close();
}
} catch (Exception e) {// TODO: handle exception}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// 生成客户端类对象
MyClient myClient = new MyClient();
myClient.getClient();}
}
同时要留神:在客户端和服务器搭建胜利之后,应该先关上服务器期待连贯,再关上客户端进行连贯,同样在进行敞开时,应该先敞开客户端,再敞开服务器。
以下面程序为例:
关上服务器期待客户端连贯
关上客户端与服务器连贯胜利,并且实现双向通信:
留神:当一台机器上装置了多个网络应用程序时,很可能指定的端口曾经被占用,甚至还可能遇到之前运行很好的程序忽然卡住的状况,这种状况很可能是端口被别的程序占用了,这时能够运行 netstat-help 来活的帮忙,能够应用命令 netstat-an 来查看该程序所应用的端口。
点击关注,第一工夫理解华为云陈腐技术~