TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
1.TCP协议
TCP协议是英文Transmission Control Protocol的缩写,即传输控制协议。使用TCP协议进行传输时,TCP协议会在两个端点建立一个连接。所以被称为端到端协议,它是可靠的。
使用TCP传输,需要客户端和服务端。
TCP特点:面向连接的、可靠的。
2.客户端Socket
使用TCP通信的两端有客户端和服务端的区分。客户端通常可使用Socket来作为客户端。
构造器:
Socket(InetAddress address, int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(String host, int port):创建一个流套接字并将其连接到指定主机上的指定端口号。
客户端和服务端建立连接后,就有了对应的Socket流,所以不需要再创建流,只要获取流就可以进行通信了。
方法;
InputStream getInputStream():返回此套接字的输入流。
OutputStream getOutputStream():返回此套接字的输出流。
InetAddress getInetAddress():返回套接字连接的地址。
void shutdownOutput():用于在输出数据结束时写一个结束标识发送给客户端,告诉客户端通信结束,以便客户端停止接收。
3.服务端ServerSocket
在两个通信实体没有建立连接之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其他通信实体的连接请求。
Java通过ServerSocket来表示服务端的Socket。ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。
ServerSocket有一个特点:本身没有流对象,在与一个客户端连接后,使用客户端的流对象,这样避免了不同客户端之间的通信冲突。
构造器:
ServerSocket(int port):创建绑定到特定端口的服务器套接字。
方法:
Socket accept():如果接收到一个客户端Socket的连接请求,该方法返回一个与客户端Socket对应的Socket;负责该方法将一直处于等待状态,线程也被阻塞。
4.步骤
4.1 客户端
1)建立客户端Socket。
2)获取Socket流的输出流用于发送数据。
3)使用输出流向服务端发送数据。
4)关闭资源。
4.2 服务端
1)建立服务端Socket——ServerSocket。
2)使用accept方法与客户端建立连接。
3)获取客户端的输入流用于接收数据。
4)使用输入流接收客户端数据。
5)关闭客户端。
6)关闭服务端,可选。
例1 下面程序简易介绍客户端向服务端发送数据:发送一句话
//客户端
public class TCPClient {
public static void main(String[] args) throws Exception{
//1.建立Socket
Socket s = new Socket("192.168.0.102", 1000);
//2.获取输出流
OutputStream out = s.getOutputStream();
//3.向输出流写入数据
out.write("hello,server".getBytes());
//4.获取输入流,接收服务器反馈数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
//5.关闭资源
s.close();
}
}
//服务端
public class TCPServer {
public static void main(String[] args) throws Exception{
//1.建立服务端Socket
ServerSocket ss = new ServerSocket(1000);
//2.获取客户端Socket
Socket s = ss.accept();
//3.获取输入流接收数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf, 0, len));
//4.获取输出流
OutputStream out = s.getOutputStream();
out.write("hello,client".getBytes());
//5.关闭客户端
s.close();
}
}
例2 客户端向服务端发送客户端的键盘录入数据。
//客户端
public class TCPClient {
public static void main(String[] args) throws Exception{
//创建客户端Socket
Socket s = new Socket("192.168.0.102", 1000);
//获取Socket流中的输出流,向服务端写入数据
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
//获取Socket流中的输入流,接收服务端数据
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取键盘输入
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = bufr.readLine()) != null){
if("over".equals(line))
break;
//向服务端写入数据
out.println(line);
//获取服务端的反馈数据
String str = in.readLine();
System.out.println("server:");
System.out.println(str);
}
//关闭资源
bufr.close();
s.close();
/*
* s.close()语句执行后,会在Socket流中加入-1,这时候服务端还在循环读取,读到-1,读取结束。
* 向下执行到ss.close,服务端也会结束。
* */
}
}
//服务端
public class TCPServer {
public static void main(String[] args) throws Exception{
//建立服务端Socket
ServerSocket ss = new ServerSocket(1000);
//获取客户端Socket对象
Socket s = ss.accept();
//获取客户端ip
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "客户已连接");
//获取输入流来获取客户端数据
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取输出流来向客户端反馈数据
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = in.readLine()) != null){
System.out.println(line);
out.println(line.toUpperCase());
}
//关闭资源
s.close();
ss.close();
}
}
例3 服务端将客户端发送的文本文件保存在本地。
//客户端
public class TextClient {
public static void main(String[] args) throws Exception{
//建立客户端Socket
Socket s = new Socket("192.168.0.102", 1000);
//读取本地文本文件
BufferedReader bufr = new BufferedReader(new FileReader("d:\\demo.txt"));
//获取Socket流中输出流并包装
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = bufr.readLine()) != null){
out.println(line);
}
//读取文件遇到文件末尾可以结束循环
//但是服务端不会自动结束循环,所以要加上标记告诉服务端上传结束
s.shutdownOutput();
//获取Socket流的输入流以便获取服务端发聩信息
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String info = in.readLine();
System.out.println(info);
//关闭资源
bufr.close();
s.close();
}
}
//服务端
public class TextServer {
public static void main(String[] args) throws Exception{
//建立服务端Socket
ServerSocket ss = new ServerSocket(1000);
//获取客户端Socket建立连接
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "---客户已连接");
//获取输入流并包装
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
//保存到本地
PrintWriter pw = new PrintWriter(new FileWriter("demo.txt"));
String line = null;
while((line = in.readLine()) != null){
pw.println(line);
}
//上次完毕后向客户端反馈
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
out.println("上传完毕");
//关闭资源
pw.close();
s.close();
ss.close();
}
}
例4 使用多线程,多个客户端向服务端发送图片,将图片保存在服务端。
//客户端
public class PicClient {
public static void main(String[] args) throws Exception{
//将文件路径作为参数在运行程序时输入
//如果传入参数不等于1,传入错误
if(args.length != 1){
System.out.println("请写入正确的文件路径");
}
File file = new File(args[0]);
//如果文件不存在或者不是文件,提示错误
if(!(file.exists() && file.isFile())){
System.out.println("不是文件或文件不存在");
return;
}
//如果不是图片文件,提示错误
if(!file.getName().endsWith(".png")){
System.out.println("不是png格式的图片,请从新选择");
return;
}
//如果文件大于5M,提示错误
if(file.length() > 1024*1024*5){
System.out.println("文件过大");
return;
}
//建立客户端Socket
Socket s = new Socket("192.168.0.102", 1000);
//读取图片文件
FileInputStream fis = new FileInputStream(file);
//获取网络流的输出流上传文件
OutputStream out = s.getOutputStream();
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1){
out.write(buf, 0, len);
}
//给服务端一个结束标识使服务端停止接收
s.shutdownOutput();
//获取网络流的输入流以便接收服务端反馈信息
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = in.readLine();
System.out.println(line);
//关闭资源
fis.close();
s.close();
}
}
//服务端的线程类,每个线程类负责处理一个客户端请求
public class PicThread implements Runnable{
//客户端
private Socket s;
public PicThread(Socket s){
this.s = s;
}
public void run(){
//计数器,用于记录文件名的编号
int count = 1;
String ip = s.getInetAddress().getHostAddress();
FileOutputStream fos = null;
try{
System.out.println(ip + "已连接");
//以客户端ip地址为图片命名,如果该名称已存在,那么加上编号
File file = new File(ip + ".png");
while(file.exists()){
file = new File(ip + "(" + (count++) + ").png");
}
//获取网络流中的输入流
InputStream in = s.getInputStream();
//向本地写数据
fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) != -1){
fos.write(buf, 0, len);
}
//向客户端反馈信息
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
out.println("上传成功");
}catch(Exception e){
throw new RuntimeException(ip + "上传失败");
}finally{
//关闭资源
try{
if(fos != null)
fos.close();
}catch(IOException e){
e.printStackTrace();
}
try{
s.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
//服务端
public class PicServer2 {
public static void main(String[] args) throws Exception{
//服务端
ServerSocket ss = new ServerSocket(1000);
while(true){
//连接客户端
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
例5
客户端使用键盘录入用户名。服务端进行校验。
如果存在,服务端显示**已登录,客户端显示欢迎
不存在,服务端显示**尝试登录,客户端显示用户名不存在
最多登录三次。
//客户端
public class UserClient {
public static void main(String[] args) throws Exception{
//客户端
Socket s = new Socket("192.168.0.102", 1000);
//获取键盘录入
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//获取网络流的输出流
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
//获取服务端反馈的信息
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
//循环三次
for(int i = 0; i < 3; i++){
//获取键盘录入的用户名
String user = bufr.readLine();
//如果用户名为空,退出
if(user == null)
break;
//将用户名发送到服务端
out.println(user);
//获取服务端的反馈
String info = in.readLine();
System.out.println(info);
//如果反馈信息包含“成功”,说明登录成功,退出登录
if(info.contains("成功"))
break;
}
//关闭资源
bufr.close();
s.close();
}
}
//用户线程类,用来处理每个客户端的请求
public class UserThread implements Runnable{
private Socket s;
public UserThread(Socket s){
this.s = s;
}
public void run(){
//获取客户端ip
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "已连接");
try{
//循环三次,判断用户名是否存在
for(int i = 0; i < 3; i++){
//获取客户端输入的用户名
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String user = in.readLine();
//如果用户名为空,退出
if(user == null)
break;
//读取本地数据库
BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
String name = null;
//输出流反馈信息
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
//定义标记
boolean flag = false;
//遍历数据库比对用户名
while((name = bufr.readLine()) != null){
//如果用户名存在,将标记置为真,退出判断
if(name.equals(user)){
flag = true;
break;
}
}
//判断标记
//如果标记为真,那么用户名存在
if(flag){
//服务端显示用户已登录
System.out.println(user + "已经登录");
//向客户端反馈登录成功
out.println(user + "登陆成功");
//结束三次循环判断
break;
}else{
//否则,用户名不存在
System.out.println(user + "尝试登录");
out.println("用户名不存在");
}
}
//关闭资源
s.close();
}catch(Exception e){
throw new RuntimeException(ip + "登录异常");
}
}
}
//服务端
public class UserServer {
public static void main(String[] args) throws Exception{
//服务端
ServerSocket ss = new ServerSocket(1000);
while(true){
//和客户端连接
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}