网络编程(二)TCP网络编程
1、概述
在TCP通信协议下,能实现两台计算机之间的数据交互,并且它们要严格区分客户端(Client)与服务端(Server)
客户端和服务端通信的步骤:
(1)服务端先进行启动,并占用一个指定的端口号,等待客户端的连接。
(2)客户端主动发起服务端的连接,在连接成功之后,就可以进行数据发送。
服务端不能主动连接客户端,必须由客户端先行发起连接才行
在java中,对于这样基于TCP协议下连接通信的客户端和服务端,分别进行了抽象:
java.net.Socket类表示客户端
java.net.ServerSocket类表示服务端
使用Socket和ServerSocket进行的编程,也称为套接字编程
客户端
java.net.Socket类表示客户端
public class Socket{
//构造器,需要指定要连接的服务端IP和端口号
public Socket(String host, int port){
//..
}
//返回此套接字对应的输入流,可以读取对方写过来的数据
public InputStream getInputStream() throws IOException {
//..
}
//返回此套接字对应的输出流,可以给对方写数据
public OutputStream getOutputStream() throws IOException {
//..
}
//关闭此套接字
public synchronized void close() throws IOException {
//..
}
//禁用socket.getOutputStream()方法返回的字节输出流out,但是socket不关闭
//注意,如果调用out.close()方法,那么这个socket也会关闭,查看SocketOutputStream中close方法可知
//注意,如果先关闭socket的话,这个输出流out就不能用了
public void shutdownOutput() throws IOException{
//...
}
//禁用socket.getInputStream()方法返回的字节输入流in,但是socket不关闭
//注意,如果调用in.close()方法,那么这个socket也会关闭,查看SocketInputStream中close方法可知
//注意,如果先关闭socket的话,这个输入流in就不能用了
public void shutdownInput() throws IOException{
}
}
服务器端
java.net.ServerSocket类表示服务端
public class ServerSocket{
//构造器,需要指定服务端启动后占用的端口号
public ServerSocket(int port) throws IOException {
//..
}
//监听指定端口号,并等待客户端的连接
//该方法会一直让线程阻塞,直到有客户端连接过来
//如果有客户端连接过来,那么该方法会返回一个Socket对象,表示客户端,用于和客户端实现通信。
public Socket accept() throws IOException {
//..
}
}
2、客户端与服务器端交互
普通交互
测试一:客户端和服务端进行连接
客户端
步骤:
(1)IP地址和端口号
(2)声明Socket
(3)创键Socket对象
(4)中间操作
(5)关闭资源
import java.io.IOException;
import java.net.Socket;
public class ClientTCP {
public static void main(String[] args) {
//本机IP地址和端口号
String host = "127.0.0.1";
int port = 8888;
//声明socket
Socket socket = null;
try {
//创建Socket对象
socket = new Socket(host, port);
} catch (IOException e) {
e.printStackTrace();
} finally {
//记得关闭资源
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端:
步骤:
(1)声明端口号
(2)声明ServerSocket和Socket
(3)创建服务器对象,传入端口号
(4)accept方法阻塞线程,等待客户端的连接
(5)中间操作
(6)关闭资源
(2)(3)步骤可以合并在一起写,如,
ServerSocket server = new ServerSocket();
但此时要注意如果使用try。。catch。。。捕获异常的话,关闭资源时就比较麻烦,因为对于Socket和ServerSocket的声明都在try。。。catch。。括号里面,使用范围也就只在这对括号里面,可以选择改为抛出异常
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTCP {
public static void main(String[] args) {
//端口号要和客户端的端口号对应一致
int port = 8888;
//声明
ServerSocket server = null;
Socket socket = null;
try {
//创建服务器对象,传入端口号
server = new ServerSocket(port);
System.out.println("服务器启动,监听端口" + port + "等待客户端的连接");
//accept方法会导致线程阻塞到这,等待客户端的连接
socket = server.accept();
System.out.println("服务器接收到客户端的连接:" + socket);
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:先启动服务器端,再启动客户端
运行服务器端后
再运行客户端:
这里的输出窗口都是服务器端的,客户端的窗口没有输出
测试二:连接出成功后,客户端向服务器发送信息,服务器接收信息
客户端
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
//连接出成功后,客户端向服务器发送信息,服务器接收信息
public class ClientTCP {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8888;
String encoding = "UTF-8";
Socket socket = null;
PrintWriter pw = null;
try {
socket = new Socket(host,port);
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
//向服务器端发送信息
pw.println("你好...我是客户端Client!");
pw.flush();
}catch(IOException e){
e.printStackTrace();
}finally {
pw.close();
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//连接出成功后,客户端向服务器发送信息,服务器接收信息
public class ServerTCP {
public static void main(String[] args) {
int port = 8888;
ServerSocket server = null;
Socket socket = null;
String encoding = "UTF-8";
BufferedReader br = null;
try {
server = new ServerSocket(port);
System.out.println("等待来自客户端的连接...");
socket = server.accept();
System.out.println("连接到客户端:" + socket);
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
//调用BufferedReader的readLine方法读取一行数据,用String类型的readLine接收
String readLine = br.readLine();
//输出客户端发过来的信息
System.out.println("服务器接收到来自客户端的信息:" + readLine);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br!=null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端窗口显示如下:
测试三:连接成功后,客户端向服务器发送信息,服务器接收信息,并且写回数据给客户端,客户端再接收
客户端代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
//连接成功后,客户端向服务器发送信息,服务器接收信息,并且写回数据给客户端,客户端再接收
public class ClientTCP {
public static void main(String[] args) {
int port = 8888;
String host = "127.0.0.1";
String encoding = "UTF-8";
Socket socket = null;
//用于给服务器发送信息
PrintWriter pw = null;
//用于接收来自客户端的信息
BufferedReader br = null;
try {
socket = new Socket(host,port);
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
pw.println("你好...这条消息来自客户端...");
pw.flush();
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
String line = br.readLine();
System.out.println("接收到服务器端回应的消息:" + line);
} catch(Exception e) {
e.printStackTrace();
}finally {
pw.close();
try {
br.close();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端代码:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
//连接成功后,客户端向服务器发送信息,服务器接收信息,并且写回数据给客户端,客户端再接收
public class ServerTCP {
public static void main(String[] args) {
int port = 8888;
String encoding = "UTF-8";
Socket socket = null;
ServerSocket server = null;
//用于接收来自客户端的信息
BufferedReader br = null;
//用于给客户端写回信息
PrintWriter pw = null;
try {
server = new ServerSocket(port);
System.out.println("服务器端等待客户端...");
socket = server.accept();
System.out.println("服务器端收到来自客户端的连接请求...");
//接收客户端信息
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
String line = br.readLine();
System.out.println("服务器端收到来自客户端的信息:" + line);
//给客户端发送信息
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
pw.println("你好客户端,这条信息来自服务器端!");
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw!= null) {
pw.close();
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server != null){
try{
server.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果:
(1)启动服务器端
(2)启动客户端
在这期间,客户端向服务器端发送了消息(再去看服务器端窗口)
并且服务器端也向客户端发送了消息(打开客户端窗口)
测试四:客户端可以从控制台接收用户的输入内容,然后再写给服务器,然后再接收服务器写回来的数据
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
//客户端可以从控制台接收用户的输入内容,然后再写给服务器,然后再接收服务器写回来的数据
public class ClientTCP {
public static void main(String[] args) {
int port = 8888;
String host = "127.0.0.1";
String encoding = "UTF-8";
Socket socket = null;
BufferedReader bir = null;
PrintWriter pw = null;
try {
socket = new Socket(host,port);
//用来从控制台获取内容
bir = new BufferedReader(new InputStreamReader(System.in,encoding));
String fromConsole = bir.readLine();
//向服务端发送从控制台接收到的内容
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
pw.println(fromConsole);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bir!=null) {
try {
bir.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw != null) {
pw.close();
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
//客户端可以从控制台接收用户的输入内容,然后再写给服务器,然后再接收服务器写回来的数据
public class ServerTCP {
public static void main(String[] args) {
int port = 8888;
String encoding = "UTF-8";
Socket socket = null;
ServerSocket server = null;
BufferedReader br = null;
PrintWriter pw = null;
try {
server = new ServerSocket(port);
System.out.println("服务启动...监听端口" + port + ",等待客户端的连接");
socket = server.accept();
System.out.println("连接到了客户端:" + socket);
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
String fromClient = br.readLine();
System.out.println("服务器接收到客户端的信息:" + fromClient);
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
pw.println("欢迎访问本服务器!");
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw != null) {
pw.close();
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server != null){
try{
server.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
结果如下:
(1)启动服务器端
(2)启动客户端
此时服务器端收到了客户端发送过来建立连接的信息
接下来在客户端窗口输入要发给服务端的信息
服务器端收到了来自客户端控制台发送的信息
进阶交互
测试一:服务端启动后,接收一个客户端的多次连接(一个客户端,一个服务器,客户端不断发信息给服务端)
。。。就是测试4加上了个循环实现。。。
客户端
public static void main(String[] args) {
int port = 8888; //端口号
String host = "127.0.0.1"; //ip地址
String encoding = "UTF-8"; //编码格式
Socket socket = null;
BufferedReader brConsole = null; //用来接收来自控制台输入的信息
String fromConsole = null;
PrintWriter pw = null; //用来传信息给服务器
BufferedReader brServer = null; //用来接收来自服务器返回的信息
String fromClient = null;
try {
socket = new Socket(host,port);
//用不着重复创建各个流对象
brConsole = new BufferedReader(new InputStreamReader(System.in,encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
while(true) {
//从控制台读取信息
fromConsole = brConsole.readLine();
//客户端向服务器端发送信息
pw.println(fromConsole);
pw.flush();
//客户端接收服务器端返回的信息
fromClient = brServer.readLine();
System.out.println("客户端接收到服务器写回的是内容为:" + fromClient);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pw.close();
if(brServer != null) {
try {
brServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务器端
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
String encoding = "UTF-8"; //指定接收信息的编码格式
BufferedReader br = null;
PrintWriter pw = null;
OutputStreamWriter osw = null;
String line = null;
try {
server = new ServerSocket(8888);
System.out.println("服务器启动...");
socket = server.accept();
System.out.println("连接到客户端:" + socket );
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
while(true) {
line = br.readLine();
System.out.println("服务器端接收到客户端信息内容为:" + line);
pw.println("欢迎访问本服务器!");
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(pw != null) {
pw.close();
}
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server!=null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果演示:
(1)启动服务器端
(2)启动客户端
在客户端窗口输入要传给服务器端的信息
可以继续输入:
在服务端窗口会看到这样的显示:
测试二:服务器端同时接收多个不同客户端的连接
总体思路:
一个服务器,多个客户端,要想多个客户端可以做到向同一个服务器建立连接和发送消息,那么服务器端就必须用到多线程,当一个客户端向服务器发送连接请求时,创建一个线程用来处理这个客户端,另一个客户端向服务器向同一服务器发送请求,服务器再创建一个线程用来处理这个客户端,循环往复,服务器不断创建线程来处理多个客户端。。。
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class ClientTCP {
public static void main(String[] args) {
int port = 8888; //端口号
String host = "127.0.0.1"; //ip地址
String encoding = "UTF-8"; //编码格式
Socket socket = null;
BufferedReader brConsole = null; //用来接收来自控制台输入的信息
String fromConsole = null;
PrintWriter pw = null; //用来传信息给服务器
BufferedReader brServer = null; //用来接收来自服务器返回的信息
String fromClient = null;
try {
socket = new Socket(host,port);
//用不着重复创建各个流对象
brConsole = new BufferedReader(new InputStreamReader(System.in,encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
while(true) {
//从控制台读取信息
fromConsole = brConsole.readLine();
//客户端向服务器端发送信息
pw.println(fromConsole);
pw.flush();
//客户端接收服务器端返回的信息
fromClient = brServer.readLine();
System.out.println("客户端接收到服务器写回的是内容为:" + fromClient);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pw.close();
if(brServer != null) {
try {
brServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTCP {
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
try {
server = new ServerSocket(8888);
System.out.println("服务器启动...");
//关于为什么while加在这
//服务器端要接收客户端的连接,就要使用accept方法,当有多个客户端要建立连接时就要不断地调用这方法
while(true) {
socket = server.accept();
System.out.println("连接到客户端:" + socket );
//创建线程对象:每次传入一个客户端对象,创建对应线程
MyThread t = new MyThread(socket);
//启动线程
t.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(server!=null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
创建线程类,用于服务器处理多个客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyThread extends Thread{
Socket socket = null;
ServerSocket server = null;
String encoding = "UTF-8";
BufferedReader br = null;
PrintWriter pw = null;
String line = null;
//构造器:用于传入一个socket客户端对象
public MyThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
while(true) {
line = br.readLine();
System.out.println("服务器端接收到客户端信息内容为:" + line);
pw.println("欢迎访问本服务器!");
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(br!= null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(pw != null) {
pw.close();
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server != null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
效果演示:
(1)启动服务器端
(2)启动一个客户端(假设标号为1)
(3)客户端1向服务端发送消息
客户端发送信息:这里是客户端1发送过来的信息
(在这期间收到了服务器端的回应)
客户端窗口显示
此时服务器端收到了客户端1发送过来的信息,并回应了客户端
服务器端窗口显示
(4)再启动一个客户端(就是再次运行下客户端代码,即为再启动一个客户端。。。)(假设标号为2)
客户端2向服务器端发送信息:这里是客户端2发送过来的信息(此期间也收到了服务器端的回应)
服务器端的反映如下:
你还可以继续添加客户端,继续向服务器发送消息。。。
总结:
如果像这样使用多线程来处理的话会造成资源浪费,至于其他方法我目前暂时还未涉及到,好像线程池还可以处理,但似乎也不是很好,具体还有啥学到了时再补充。。。(懒)
测试三:客户端不断的读取控制台的数据,然后写给服务器,而不是读取一次就结束
思路:
这个和测试一是类似的。不同的是这里客户端从控制台读取数据的时候要加个表示输入结束的关键词,如end,来表示输入结束,客户端每次将回车接收到的内容传给服务器端
客户端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
//客户端不断的读取控制台的数据,然后写给服务器,而不是读取一次就结束
public class ClientTCP {
public static void main(String[] args) {
int port = 8888; //端口号
String host = "127.0.0.1"; //ip地址
String encoding = "UTF-8"; //编码格式
Socket socket = null;
BufferedReader brConsole = null; //用来接收来自控制台输入的信息
String fromConsole = null;
PrintWriter pw = null; //用来传信息给服务器
BufferedReader brServer = null; //用来接收来自服务器返回的信息
String fromClient = null;
try {
socket = new Socket(host,port);
//用不着重复创建各个流对象
brConsole = new BufferedReader(new InputStreamReader(System.in,encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
brServer = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
while(true) {
//从控制台读取信息
fromConsole = brConsole.readLine();
if(!fromConsole.equals("end")) {
//客户端向服务器端发送信息
pw.println(fromConsole);
}
else{
pw.flush();
//客户端接收服务器端返回的信息
fromClient = brServer.readLine();
System.out.println("客户端接收到服务器写回的是内容为:" + fromClient);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pw.close();
if(brServer != null) {
try {
brServer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务器端:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
//客户端不断的读取控制台的数据,然后写给服务器,而不是读取一次就结束
public class ServerTCP {
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
String encoding = "UTF-8"; //指定接收信息的编码格式
BufferedReader br = null;
PrintWriter pw = null;
OutputStreamWriter osw = null;
String line = null;
try {
server = new ServerSocket(8888);
System.out.println("服务器启动...");
socket = server.accept();
System.out.println("连接到客户端:" + socket );
br = new BufferedReader(new InputStreamReader(socket.getInputStream(),encoding));
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),encoding));
while(true) {
line = br.readLine();
System.out.println("服务器端接收到客户端信息内容为:\n" + line);
pw.println("欢迎访问本服务器!");
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(pw != null) {
pw.close();
}
if(br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(server!=null) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端窗口输入
服务端窗口
我这里是每次读取一行就输出来。。。估计还有更好一点的方法,就这样吧
码了这么多字,人都麻了,心累,身累,也还得加把劲学习,毕竟竞争太激烈了(o(╥﹏╥)o)