通过本实验,学习采用Socket(套接字)设计简单的网络数据收发程序,理解应用数据包是如何通过传输层进行传送的。
流套接字将TCP作为其端对端协议(底层使用IP协议),提供了一个可信赖的字节流服务。
程序概述:客户端与服务器之前能进行轮流信息交换,当各自回复"bye"则关闭自身,当客户端回复"ok"则关闭服务器。
客户端代码:
package com.mwx.tcp;
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws Exception {
PrintWriter socout = null;
InputStreamReader socin = null;
Socket socket = null;
try {
//1.要知道服务器的地址,端口号
InetAddress serverIP = InetAddress.getByName("10.69.51.117");
int port = 9999;
//2.创建soket
socket = new Socket(serverIP, port);
//3.发送消息
//3.1 从键盘读取要发送的消息
InputStreamReader sysin = new InputStreamReader(System.in); //输入字节流(从键盘键入)
BufferedReader sysbuf = new BufferedReader(sysin); //将输入放到缓冲区中
//3.2客户端接收来自于服务器的消息
socin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(socin);
//3.3客户端向服务器发送消息
socout = new PrintWriter(socket.getOutputStream());
//4.进行通信:
//首先是客户端向服务器发送数据,所以先等键盘键入,即获取客户端要发送的内容
String readline = sysbuf.readLine();
while (!readline.equals("bye")) { //如果我们键入的是 bye ,则停止连接 (在while循环中进行,即可一次连接,多次通信)
socout.println(readline); //将从键盘键入的内容发送给服务器
socout.flush();//flush():刷新此输出流并强制写出所有缓冲的输出字节。
//写入的时候数据是先写入内存,然后再从内从写入到文件中,之后才能从文件中读取。所以可能写入内存写完了,但是写入文件没写完,因此为了能够即时通信,需要flush()
System.out.println("Client:" + readline);
//客户端接收服务器的回复,将此回复打印出来
System.out.println("Server:" + socbuf.readLine());//readLine()方法若还没接收到任何数据,则会停在此处等待
//收到服务器的回复后,客户端继续键盘键入要发送给服务器的数据
readline = sysbuf.readLine();
}
} catch (Exception e) { //打印异常
System.out.println("Error:" + e);
} finally {//close方法也可能会有异常,这里没有捕获处理,而是先抛出去了
//完成通信后关闭io和socket
socout.close();
socin.close();
socket.close();
System.out.println("客户端已关闭");
}
}
}
服务器代码:
package com.mwx.tcp;
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws IOException {
PrintWriter socout = null;
InputStreamReader socin = null;
ServerSocket server = null;
try {
//1.建立服务器
server = new ServerSocket(9999);
//2.监听,等待客户端连接过来
Socket socket = server.accept();
//3.通信
//从键盘读取的
InputStreamReader sysin = new InputStreamReader(System.in); //输入字节流(从键盘键入)
BufferedReader sysbuf = new BufferedReader(sysin); //将输入放到缓冲区中
//接收客户端消息
socin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(socin);
//发送给客户端
socout = new PrintWriter(socket.getOutputStream());
//首先打印出客户端发来的信息,从socket缓冲区读取 (如果客户端一开始就回复ok的话不会停止通信)
System.out.println("Client:" + socbuf.readLine());
//从键盘键入服务器要回复的内容
String readline = sysbuf.readLine();
while (!readline.equals("bye")) { //只要服务器键入的不是 bye,就继续通信
//将readline发送给客户端
socout.println(readline);
socout.flush(); //刷新缓冲区
//打印出服务器回复了啥
System.out.println("Server:" + readline);
String flag = socbuf.readLine();
if (!flag.equals("ok")) //如果从客户端接收的字符不是 ok,则打印出来,若果不是则结束循环
System.out.println("Client:" + flag);
else //如果客户端发来的信息是ok,则关闭与客户端的通信
break;
readline = sysbuf.readLine();
}
} catch (Exception e) {
System.out.println("Error:" + e);
} finally {
socout.close();
socin.close();
server.close();
System.out.println("服务器已关闭");
}
}
}
运行方式:先启动服务器,再启动客户端;客户端先发送数据,服务器再回复。
数据报套接字使用UDP协议(底层同样使用IP协议),提供了一个"尽力而为"(best-effort)的数据报服务,应用程序可以通过它发送最长65500字节的个人信息。
程序概述:可以从键盘键入,通过客户端给服务器发送消息,服务器收到之后向客户端回复“ok”。
客户端代码:
package com.mwx.udp;
import java.io.*;
import java.net.*;
public class UDPClient {
public static void main(String[] args) throws IOException{
//建立socket
DatagramSocket socket = new DatagramSocket();
//从键盘读取的
InputStreamReader sysin = new InputStreamReader(System.in); //输入字节流(从键盘键入)
BufferedReader sysbuf = new BufferedReader(sysin); //将输入放到缓冲区中
try {
//准备向服务器发送数据:
String readline = sysbuf.readLine();
//创建数据包,参数:数据、数据长度、目的IP、端口号
DatagramPacket clientpacket = new DatagramPacket(readline.getBytes(),readline.getBytes().length,InetAddress.getByName("10.69.51.117"),9998);
//发送数据
socket.send(clientpacket);
//接收从服务器返回来的数据:
byte[] serverdata = new byte[1024]; //存放从服务端返回的数据
DatagramPacket serverpacket = new DatagramPacket(serverdata,serverdata.length);
socket.receive(serverpacket);
System.out.println("Server:"+new String(serverpacket.getData()));
} catch (Exception e) {
System.out.println("error:"+e);
}finally {
socket.close();
sysbuf.close();
sysin.close();
System.out.println("客户端已关闭。");
}
}
}
服务器代码:
package com.mwx.udp;
import java.io.*;
import java.net.*;
public class UDPServer {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(9998);
try{
//接收客户端的数据
byte[] clientdata = new byte[2014];
//创建数据包
DatagramPacket clientpacket = new DatagramPacket(clientdata,clientdata.length);
//接收数据
socket.receive(clientpacket);
System.out.println("Client:"+new String(clientpacket.getData()));
//向可客户端返回数据
int port = clientpacket.getPort();//获取客户端的端口号
String respose = "ok";
DatagramPacket serverpacket = new DatagramPacket(respose.getBytes(),respose.getBytes().length, InetAddress.getByName("10.69.51.117"),port);
socket.send(serverpacket);
}catch(Exception e){
System.out.println("error;"+e);
}finally{
socket.close();
System.out.println("服务器已关闭。");
}
}
}
运行方式:先启动服务器,再启动客户端;客户端先发送数据,服务器再回复。
程序概述:采取多线程编程,使服务器可以同时与多台客户端产生连接并通信。在这儿采取实现Runnable接口的方式实现多线程。
服务器线程:
package com.mwx.thread;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerThread implements Runnable{
private String clientName = "";
private Socket socket = null;
private InputStreamReader cin = null;
private PrintWriter cout = null;
boolean flag = true;
//声明构造函数,接收客户端请求socket
public ServerThread(Socket socket,String clientName){
this.socket = socket;
this.clientName = clientName;
}
@Override
public void run() {
try {
System.out.println("任务开始~");
cin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(cin);
cout = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
//首先打印出客户端发来的信息,从socket缓冲区读取 (如果客户端一开始就回复ok的话不会停止通信)
System.out.println(clientName+":" + socbuf.readLine());
while (flag) { //只要服务器键入的不是 bye,就继续通信
//发送给客户端
cout.println("收到~");
String flag = socbuf.readLine();
if (!flag.equals("ok")) //如果从客户端接收的字符不是 ok,则打印出来,若果不是则结束循环
System.out.println(clientName+":" + flag);
else //如果客户端发来的信息是ok,则关闭与客户端的通信
break;
}
}catch (Exception e){
flag = false;
e.printStackTrace();
}finally {
closeSourse(socket,cin,cout);
System.out.println("服务器已关闭");
}
}
public static void closeSourse(Socket socket,InputStreamReader cin,PrintWriter cout){
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cin != null){
try {
cin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cout != null){
cout.close();
}
}
}
服务器:
package com.mwx.thread;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
boolean flag = true;
try{
serverSocket = new ServerSocket(9898);
System.out.println("服务器启动:"+serverSocket);
while(flag){
//阻塞式等待请求
socket = serverSocket.accept();
System.out.println("得到客户端地址:"+socket.getInetAddress());
String clientName = socket.toString();
//每当接收到一个客户端的连接请求,就创建一个线程去处理该客户端的请求
new Thread(new ServerThread(socket,clientName)).start();
}
}catch (Exception e){
flag = false;
e.printStackTrace();
}
}
}
客户端:(和单线程时是一样的)
package com.mwx.thread;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
PrintWriter cout = null;
InputStreamReader cin = null;
Socket socket = null;
boolean flag = true;
try{
socket = new Socket("10.68.164.18",9898);
System.out.println("Socket = "+socket);
//从键盘读取要发送的消息
InputStreamReader sysin = new InputStreamReader(System.in); //输入字节流(从键盘键入)
BufferedReader sysbuf = new BufferedReader(sysin); //将输入放到缓冲区中
//用来客户端向服务器发送消息
cout = new PrintWriter(socket.getOutputStream());
//用来客户端接收来自于服务器的消息
cin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(cin);
//首先是客户端向服务器发送数据,所以先等键盘键入,即获取客户端要发送的内容
String readline = sysbuf.readLine();
while (!readline.equals("bye")) { //如果我们键入的是 bye ,则停止连接 (在while循环中进行,即可一次连接,多次通信)
cout.println(readline); //将从键盘键入的内容发送给服务器
cout.flush();//flush():刷新此输出流并强制写出所有缓冲的输出字节。
//写入的时候数据是先写入内存,然后再从内从写入到文件中,之后才能从文件中读取。所以可能写入内存写完了,但是写入文件没写完,因此为了能够即时通信,需要flush()
System.out.println("Client:" + readline);
//客户端接收服务器的回复,将此回复打印出来
System.out.println("Server:" + socbuf.readLine());//readLine()方法若还没接收到任何数据,则会停在此处等待
//收到服务器的回复后,客户端继续键盘键入要发送给服务器的数据
readline = sysbuf.readLine();
}
}catch (Exception e){
e.printStackTrace();
}finally {
closeSourse(socket,cin,cout);
System.out.println("客户端已关闭");
}
}
public static void closeSourse(Socket socket,InputStreamReader cin,PrintWriter cout){
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cin != null){
try {
cin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cout != null){
cout.close();
}
}
}
运行方式:先启动服务器,然后分别启动两个客户端,从客户端发送请求,即可接受到服务器的响应。
程序概述:这个程序依然是实现一个服务器可并行处理多个客户端的请求,只是在创建线程的时候使用了线程池技术,线程池里存放的是已经创建好的线程,当需要创建线程时,就从线程池里取,当需要关闭线程时就将线程归还给线程池,这样可以避免频繁的创建和销毁线程。
服务器线程:(和多线程情况下是一样的,依然是实现Runnable接口)
package com.mwx.threadPool;
import java.io.*;
import java.net.Socket;
public class ServerThread implements Runnable{
private Socket socket = null;
private InputStreamReader cin = null;
private PrintWriter cout = null;
boolean flag = true;
//声明构造函数,接收客户端请求socket
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
System.out.println("任务开始~");
cin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(cin);
cout = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
//首先打印出客户端发来的信息,从socket缓冲区读取 (如果客户端一开始就回复ok的话不会停止通信)
System.out.println("Client:" + socbuf.readLine());
while (flag) { //只要服务器键入的不是 bye,就继续通信
//发送给客户端
cout.println("收到~");
String flag = socbuf.readLine();
if (!flag.equals("ok")) //如果从客户端接收的字符不是 ok,则打印出来,若果不是则结束循环
System.out.println("Client:" + flag);
else //如果客户端发来的信息是ok,则关闭与客户端的通信
break;
}
}catch (Exception e){
flag = false;
e.printStackTrace();
}finally {
closeSourse(socket,cin,cout);
System.out.println("服务器已关闭");
}
}
public static void closeSourse(Socket socket,InputStreamReader cin,PrintWriter cout){
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cin != null){
try {
cin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cout != null){
cout.close();
}
}
}
服务器:
package com.mwx.threadPool;
import com.mwx.thread.ServerThread;
import jdk.net.Sockets;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket= null;
ExecutorService executorService = null;
boolean flag = true;
//创建容量为2的线程池
executorService = Executors.newFixedThreadPool(2);
try {
serverSocket = new ServerSocket(9797);
System.out.println("服务器启动:"+serverSocket);
while(flag){
//阻塞式等待请求
socket = serverSocket.accept();
System.out.println("得到客户端地址:"+socket.getInetAddress());
String clientName = socket.toString();
ServerThread serverThread = new ServerThread(socket,clientName);
executorService.execute(serverThread);
}
}catch (Exception e){
e.printStackTrace();
executorService.shutdown();
}
}
}
客户端:(与前述一样)
package com.mwx.threadPool;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
PrintWriter cout = null;
InputStreamReader cin = null;
Socket socket = null;
boolean flag = true;
try{
socket = new Socket("10.63.185.101",9898);
System.out.println("Socket = "+socket);
//从键盘读取要发送的消息
InputStreamReader sysin = new InputStreamReader(System.in); //输入字节流(从键盘键入)
BufferedReader sysbuf = new BufferedReader(sysin); //将输入放到缓冲区中
//用来客户端向服务器发送消息
cout = new PrintWriter(socket.getOutputStream());
//用来客户端接收来自于服务器的消息
cin = new InputStreamReader(socket.getInputStream());
BufferedReader socbuf = new BufferedReader(cin);
//首先是客户端向服务器发送数据,所以先等键盘键入,即获取客户端要发送的内容
String readline = sysbuf.readLine();
while (!readline.equals("bye")) { //如果我们键入的是 bye ,则停止连接 (在while循环中进行,即可一次连接,多次通信)
cout.println(readline); //将从键盘键入的内容发送给服务器
cout.flush();//flush():刷新此输出流并强制写出所有缓冲的输出字节。
//写入的时候数据是先写入内存,然后再从内从写入到文件中,之后才能从文件中读取。所以可能写入内存写完了,但是写入文件没写完,因此为了能够即时通信,需要flush()
System.out.println("Client:" + readline);
//客户端接收服务器的回复,将此回复打印出来
System.out.println("Server:" + socbuf.readLine());//readLine()方法若还没接收到任何数据,则会停在此处等待
//收到服务器的回复后,客户端继续键盘键入要发送给服务器的数据
readline = sysbuf.readLine();
}
}catch (Exception e){
e.printStackTrace();
}finally {
closeSourse(socket,cin,cout);
System.out.println("客户端已关闭");
}
}
public static void closeSourse(Socket socket,InputStreamReader cin,PrintWriter cout){
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cin != null){
try {
cin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(cout != null){
cout.close();
}
}
}
运行过程与截图均与多线程实现是一样的。
服务器
package com.mwx.chat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6464);
System.out.println("~~~~~~ 服务器已启动 ~~~~~~");
//while(true){
System.out.println("等待接收文件~");
Socket socket = serverSocket.accept();
System.out.println(socket.toString()+"已连接");
new Thread(new ServerThread(socket)).start();
// }
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器线程
package com.mwx.chat;
import java.io.*;
import java.net.Socket;
public class ServerThread implements Runnable{
private Socket socket = null;
private InputStream inputStream ;
public ServerThread(Socket socket){
this.socket = socket;
try {
inputStream = socket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
//1.使用DataInputStream包装输入流
DataInputStream dataInputStream = new DataInputStream(inputStream);
try {
String fileName = dataInputStream.readUTF();
System.out.println("文件名为:"+fileName);
//往某个位置写入文件
FileOutputStream fileOutputStream = new FileOutputStream("C:"+ File.separator+fileName);
int c = -1;
while((c = dataInputStream.read()) != -1){
fileOutputStream.write(c);
fileOutputStream.flush();
}
System.out.println("文件上传成功!");
close(socket,inputStream,dataInputStream);
} catch (IOException e) {
e.printStackTrace();
close(socket,inputStream,dataInputStream);
}
}
public static void close(Socket socket,InputStream inputStream,DataInputStream dataInputStream){
try {
socket.close();
inputStream.close();
dataInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
package com.mwx.chat;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
File file = new File("D:\\计网知识点大纲.png");
try {
Socket socket = new Socket("localhost",6464);
OutputStream outputStream = socket.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
//向服务器端传文件名
dataOutputStream.writeUTF(file.getName());
dataOutputStream.flush();
//向服务器端传文件,通过字节流
//字节流先读取硬盘文件
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
int c = -1;
while((c = bufferedInputStream.read()) != -1){
//将读取的文件以字节方式写入DataOutputStream,之后传输到服务端
dataOutputStream.write(c);
dataOutputStream.flush();
}
System.out.println("文件已发送~");
close(socket,outputStream,dataOutputStream,bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void close(Socket socket,OutputStream outputStream,DataOutputStream dataOutputStream, BufferedInputStream bufferedInputStream){
try {
socket.close();
outputStream.close();
dataOutputStream.close();
bufferedInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1、基于JAVA的Socket类的TCP网络编程
2、基于JAVA的socket类的UDP网络编程
3、【狂神说Java】网络编程实战讲解
4、Java基础-IO框架
5、【狂神说Java】多线程详解