1. 网络编程基础
所谓的网络资源,其实就是在网络中可以获取的各种数据资源。 而所有的网络资源,都是通过网络编程来进行数据传输的。
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。 当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据, 也属于网络编程。特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。
但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:
- 进程A:编程来获取网络资源
- 进程B:编程来提供网络资源
关于发送端和接收端: 在一次网络数据传输时:
- 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
- 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
- 收发端:发送端和接收端两端,也简称为收发端。
- 注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
2. Socket套接字
概念:Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
分类: Socket套接字主要针对传输层协议划分为如下三类:
2.1 流套接字:使用传输层TCP协议
(TCP,即Transmission Control Protocol(传输控制协议),传输层协议。) 以下为TCP的特点:
- 有连接 :类似于打电话,需要自己拨号和对方接电话
- 可靠传输 :对方有没有收到数据是可知的
- 面向字节流 :一个字节一个一节(字节数组)为单位进行数据传输
- 有接收缓冲区,也有发送缓冲区
- 大小不限
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情 况下,是无边界的数据,可以多次发送,也可以分开多次接收。
2.2 数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。 以下为UDP的特点:
- 无连接 (不需要建立连接即可直接发送数据,类似于发微信)
- 不可靠传输 (对方有没有接收到数据是不可知的)
- 面向数据报 (以数据报为单位进行数据传输)
- 有接收缓冲区,无发送缓冲区
- 大小受限:一次最多传输64k
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一 次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。
3. Java数据报套接字通信模型
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数 据报,一次接收全部的数据报。
java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:
4. Java流套接字通信模型
5. UDP数据报套接字编程
5.1 DatagramSocket API
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。 DatagramSocket 构造方法: DatagramSocket 方法:
5.2 DatagramPacket API
DatagramPacket是UDP Socket发送和接收的数据报。 DatagramPacket 构造方法: DatagramPacket 方法: 构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创 建。
5.3 InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
5.4 UdpEchoServer(回显模式下的udp服务器)
回显模式:客服端数据怎么发送给服务器的,服务器就怎么返回数据给客服端
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
private DatagramSocket socket=null;
public UdpEchoServer(int port) throws SocketException {
socket=new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("启动服务器!");
while (true){
DatagramPacket requestPacket=new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
String request=new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");
String response=process(request);
DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length
,requestPacket.getSocketAddress());
socket.send(responsePacket);
System.out.printf("[%s:%d] req:%s,resp:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server=new UdpEchoServer(9090);
server.start();
}
}
5.5 UdpEchoClient (回显模式下的udp客服端)
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket=null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String ip,int port) throws SocketException {
socket=new DatagramSocket();
serverIP=ip;
serverPort=port;
}
public void start() throws IOException{
Scanner scanner=new Scanner(System.in);
while (true){
System.out.print("-> ");
String request=scanner.next();
DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length
, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket=new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);
String response=new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
System.out.printf("req:%s,resp:%s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client=new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
运行代码: 客服端: 服务器:(IP:端口号) 注意:勾选运行代码设置模块下的这个按钮就可以创建出多个不同端口号的客服端程序: 不同客服端程序被创建出来所被绑定的端口号也是不同的,此处由系统自动分配:
5.6 UdpDictServer (Udp服务器-简单实现翻译功能)
这个实现翻译功能的服务器直接继承自UdpEchoServer 类,只需要重写一下process 加工数据方法的业务逻辑即可:
package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
public class UdpDictServer extends UdpEchoServer{
private HashMap<String,String> dict=new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("cat", "小猫");
dict.put("dog", "小狗");
dict.put("fuck", "卧槽");
dict.put("pig", "小猪");
}
@Override
public String process(String request) {
return dict.getOrDefault(request,"该词无法被翻译");
}
public static void main(String[] args) throws IOException {
UdpDictServer server=new UdpDictServer(9090);
server.start();
}
}
客服端服务器通用上一个:
6. TCP流套接字编程
6.1 ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。 ServerSocket 构造方法: ServerSocket 方法:
ServerSocket API 只用于接收从客服端传过来的程序(类似于只完成接电话这一操作)
6.2 Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
Socket 构造方法:
Socket 方法:
6.3TcpEchoServer(回显模式下的Tcp服务器)
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer {
private ServerSocket serverSocket=null ;
public TcpEchoServer(int port) throws IOException{
serverSocket=new ServerSocket(port);
}
public void start() throws IOException{
System.out.println("服务器启动!");
while (true){
Socket clientSocket=serverSocket.accept();
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端建立连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try(InputStream inputStream=clientSocket.getInputStream()) {
try(OutputStream outputStream=clientSocket.getOutputStream()) {
Scanner scanner=new Scanner(inputStream);
while (true){
if (!scanner.hasNext()){
System.out.printf("%s:%d 客户端断开连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
String request=scanner.next();
String response=process(request);
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] req:%s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort()
,request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
clientSocket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server=new TcpEchoServer(9090);
server.start();
}
}
6.4 TcpEchoClient(回显模式下的Tcp客服端)
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket=null;
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
socket=new Socket(serverIP,serverPort);
}
public void start(){
System.out.println("和服务器建立连接成功!");
Scanner scanner=new Scanner(System.in);
try(InputStream inputStream=socket.getInputStream()) {
try(OutputStream outputStream=socket.getOutputStream()) {
while (true){
System.out.print("-> ");
String request=scanner.next();
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
Scanner respScanner=new Scanner(inputStream);
String response=respScanner.next();
System.out.printf("req:%s ,resp:%s",request,response);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client=new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
运行程序: (相当于传给服务器两条数据,一条hello,一条tcp,服务器回显给客服端原样数据) 客服端: 服务器:
6.5 TcpThreadEchoServer (多线程实现可以允许有多个客服端发生请求)
由于上面的Tcp服务器存在一定的问题,上述服务器存在两个循环,外层循环用来建立连接,建立连接成功之后就会进入内层循环来读取客服端请求,那么内层的读取客服端请求的循环不结束,外层循环就不会进行,这样就导致了这个服务器只能与一个客服端建立连接成功,这显然是不合常理的;
解决这个问题的方法就是运用多线程,在主线程中进行与客服端建立连接,一旦连接建立成功,接下来的读取客服端请求的代码就交给一个线程执行,这样两个线程并发执行,互不干扰,就实现了可以同时读取多个客服端的请求,解决了问题~
下面的运用创建多个线程进行读取客服端请求的代码:
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpThreadEchoServer {
private ServerSocket serverSocket=null ;
public TcpThreadEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException{
System.out.println("服务器启动!");
while (true){
Socket clientSocket=serverSocket.accept();
Thread t=new Thread(()->{
processConnection(clientSocket);
});
t.start();
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端建立连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try(InputStream inputStream=clientSocket.getInputStream()) {
try(OutputStream outputStream=clientSocket.getOutputStream()) {
Scanner scanner=new Scanner(inputStream);
while (true){
if (!scanner.hasNext()){
System.out.printf("%s:%d 客户端断开连接!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
String request=scanner.next();
String response=process(request);
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] req:%s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort()
,request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
clientSocket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer server=new TcpThreadEchoServer(9090);
server.start();
}
}
6.6 TcpThreadPoolEchoServer(线程池实现可以允许有多个客服端发生请求)
这个与上面创建多个线程执行读取客服端请求的代码类似,只是把创建多个线程改成了运用线程池,提升了效率 ~
代码如下:
package network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpThreadPoolEchoServer {
private ServerSocket serverSocket = null;
public TcpThreadPoolEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool=Executors.newCachedThreadPool();
while (true) {
Socket clientSocket = serverSocket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream()) {
try (OutputStream outputStream = clientSocket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
while (true) {
if (!scanner.hasNext()) {
System.out.printf("[%s:%d] 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
String request = scanner.next();
String response = process(request);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(), request, response);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
server.start();
}
}
执行:
客服端1 客服端2 服务器 通过运用多线程的改进,服务器就可以同时与多个客服端建立连接并读取返回客服端请求了,tcp服务器实现成功 ~
|