基础概念
1.发送端与接受端
在通过网络传输信息时,会有两个进程,接收端和发送端。
发送端:数据的发送方进程,即网络通信中的源主机。
接收端:数据的接收方进程,即网路通信中的目的主机。
2.Socet套接字
Socket套接字,是由系统提供用于网络通信的技术。基于Socket套接字的网络程序开发就是网络编程。
Socket主要分为流套接字和数据报套接字,还有原始套接字(应用很少,不介绍)
流套接字:使用传输层协议Tcp,Tcp特点:1.有连接。2.面向字节流。3.可靠传输。4.有接受缓冲区,也有发送缓冲区。5.传输信息大小不限。6.全双工。
数据报套接字:使用传输层协议Udp,Udp特点:1.无连接。2.面向数据报。3.不可靠传输。4.有接受缓冲区,无发送缓冲区。5.一次最多传输64KB。6.全双工。
学习网络编程,本质上就是学习传输层提供给应用层的API,通过API来实现客户端和服务器。
UDP socketAPI 的使用
由于Udp是面向数据报的,所以核心的类都是和数据报有关的。
1.DategramSocket
分为两个版本,1.DategramSocket(),创建一个Udp数据报套接字的Socket,绑定到主机上任意一个空闲端口,一般用于客户端。2.DategramSocket(int port) :用于指定绑定的端口, 通过port传入,一般用于服务端。
2.send(DategramPacket p) 和 receive(DategramPacket p)
send:将数据打包为DategramPacket类型后发送出去,不会阻塞等待。
receive:从另一方接受DategramPacket类型的数据,如果还没收到会阻塞等待。
3.DategramPacket
由于UDP面向数据报,所以每次发送和接受都是一个DategramPacket类型的数据报,DategramPacket是UDP发送和接收的基本单位。在Udp中,传入的参数是1.byte[]数组(可以通过调用String.Byte()来转化);2.byte[]数组的长度,也可以通过String.Byte().length来传入;3.端口号,如果是客户端还需要传入服务器的IP地址。这个需要注意二者的不同。
实例:基于UDP实现一个简单的回显器
一.客户端
1.首先创建客户端要连接的服务器的端口号和IP地址,并在构造函数中将它们传入
package netWork;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;
public UDPEchoClient(int port, String Ip) throws SocketException {
Serverport = port;
ServerIp = Ip;
this.socket = new DatagramSocket();
}
}
2.创建start方法,通过Scannner函数读取用户传入的信息。
public void start(){
System.out.println("客户端启动!");
Scanner in = new Scanner(System.in);
}
3.创建一个while循环,将用户传入的信息进行打包为DategramPacket类型,然后发送给服务器。由于我们传入的IP号是String类型,所以需要通过InetAddres.getByName()方法来转换一下。
while (true){
System.out.println("请输入:");
String request = in.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
socket.send(requestPacket);
}
同时注意一下异常的抛出,这里由于涉及到了IO的输入与输出,异常抛出是在所难免的。
4.发送完数据后,我们还需要创建一个DategramPacket类型的数据报来接受服务器传来的数据,同时将传来的数据转换为String类型并打印。这里我创建了一个大小为4096的byte[]数组来接收。
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner in = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String request = in.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
socket.send(requestPacket);
DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responesPacket);
String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
System.out.println(respones);
}
}
5.到这里差不多就写完了,我们还需要创建主类,调用stat方法就行了。(127.0.0.1为本机IP地址,由于实例是一个机器同时扮演服务器和客户端,所以用本机地址,而9999为随意指定的服务器端口号,只要是空闲的,不是知名端口号即可(>1024))
public static void main(String[] args) throws IOException {
UDPEchoClient udpEchoClient = new UDPEchoClient(9999,"127.0.0.1");
udpEchoClient.start();
}
整体代码
package netWork;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPEchoClient {
private int Serverport = 0;
private String ServerIp = "";
DatagramSocket socket = null;
public UDPEchoClient(int port, String Ip) throws SocketException {
Serverport = port;
ServerIp = Ip;
this.socket = new DatagramSocket();
}
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner in = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String request = in.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),Serverport);
socket.send(requestPacket);
DatagramPacket responesPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responesPacket);
String respones = new String(responesPacket.getData(),0,responesPacket.getLength());
System.out.println(respones);
}
}
public static void main(String[] args) throws IOException {
UDPEchoClient udpEchoClient = new UDPEchoClient(9999,"127.0.0.1");
udpEchoClient.start();
}
}
二、服务端
1.与客户端类似,先创建DatagramSocket类型变量,再通过构造函数来绑定端口号。
public class UDPEchoServer {
DatagramSocket socket = null;
public UDPEchoServer(int port) throws SocketException {
this.socket = new DatagramSocket(port);
}
}
2.创建start方法,不过与客户端不同的是服务器是先接收,处理后再发送出去,其他的与客户端大同小异。
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String spones = process(request);
DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
socket.send(sponesPacket);
}
}
3.同时为了显示出数据的发送信息,我在最后加了一句打印信息的代码。而process方法则就是返回原字符串,印证了回显器的功能。
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String spones = process(request);
DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
socket.send(sponesPacket);
System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
}
}
private String process(String request) {
return request;
}
4.最后创建主类,调用方法即可。
package netWork;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPEchoServer {
DatagramSocket socket = null;
public UDPEchoServer(int port) throws SocketException {
this.socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String spones = process(request);
DatagramPacket sponesPacket = new DatagramPacket(spones.getBytes(),spones.getBytes().length,requestPacket.getSocketAddress());
socket.send(sponesPacket);
System.out.printf("[%s : %d],req: %s,spon: %s\n",requestPacket.getAddress(),requestPacket.getPort(),request,spones);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UDPEchoServer udpEchoServer = new UDPEchoServer(9999);
udpEchoServer.start();
}
}
效果:
TCP socketAPI 的使用
1.ServerSocket
Socket类,对应到网卡,但是只可以给服务器使用。
2.Socket
也是对应到网卡,既可以给服务器使用,又可以给客户端使用。
在开发中二者我们都要用到。
3.accept()方法
Socket类中一个重要的方法,由于TCP是有连接的,所以我们需要accept来确认连接。如果没有客户端连接过来,accept也会进入阻塞状态。
实例:同样基于TCP实现一个回显器
一、服务端
1.同UDP服务器一样,先绑定端口号。
ServerSocket socket = null;
public TcpServer(int port) throws IOException {
socket = new ServerSocket(port);
}
2.再使用Socket来创建一个变量,用来在建立连接后与客户端的通信,同时为了能与多个客户端同时通信,而且避免较大的线程开销,我们使用线程池来为每个客户端进程创建一个线程。
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
Socket clientSocket = socket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
processConnectin(clientSocket);
}
});
}
}
3.实现processConnection方法:首先打印连接到的客户机的信息,并提醒其上线了,由于TCP是面向字节流的,所以我创建了InputStream和OutputStream对象来接收和传输数据。这里在try括号里创建,好处是try-catch的结尾自动调用close方法,避免了占用过多的文件描述符。
private void processConnectin(Socket clientSocket) {
System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
while(true){
Scanner in = new Scanner(inputStream);
if(!in.hasNext()) {
System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
String request = in.next();
String sponse = process(request);
}
}catch (IOException e){
throw new RuntimeException(e);
}
}
4.对于要发送信息的处理,我们可以使用OutputStream的方法,但是更好的方法是使用PrintWrite来封装一下outputstream,一方面是好处理换行,另一方面是因为TCP有发送缓存,如果我发送的信息太少的话,它是会先存到缓存中而不去发送,而PrintWrite的flush方法可以强制将缓冲区中的数据立即写入到关联的输出流中。同时不要忘记关闭clientsocket。
private void processConnectin(Socket clientSocket) {
System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
while(true){
Scanner in = new Scanner(inputStream);
if(!in.hasNext()) {
System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
clientSocket.getPort());
break;
}
String request = in.next();
String sponse = process(request);
PrintWriter writer = new PrintWriter(outputStream);
writer.println(sponse);
writer.flush();
System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
clientSocket.getPort(),request,sponse);
}
}catch (IOException e){
throw new RuntimeException(e);
}finally {
try{
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
5.最后创建主类,调用方法即可。
package Tcp;
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;
import java.util.concurrent.ThreadPoolExecutor;
public class TcpServer {
ServerSocket socket = null;
public TcpServer(int port) throws IOException {
socket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
Socket clientSocket = socket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
processConnectin(clientSocket);
}
});
}
}
private void processConnectin(Socket clientSocket) {
System.out.printf("[%s : %d]客户机上线了!\n",clientSocket.getInetAddress(),clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
while(true){
Scanner in = new Scanner(inputStream);
if(!in.hasNext()) {
System.out.printf("[%s : %d]客户机已经下线!\n",clientSocket.getInetAddress(),
clientSocket.getPort());
break;
}
String request = in.next();
String sponse = process(request);
PrintWriter writer = new PrintWriter(outputStream);
writer.println(sponse);
writer.flush();
System.out.printf("[%s : %d]req: %s,spon: %s\n",clientSocket.getInetAddress(),
clientSocket.getPort(),request,sponse);
}
}catch (IOException e){
throw new RuntimeException(e);
}finally {
try{
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpServer tcpServer = new TcpServer(9998);
tcpServer.start();
}
}
二、客户端
1.首先创建Socket和构造函数,值得注意的是TCP是有连接的,所以Socket可以记住ip和port,不需要在类中创建这些变量。
private Socket socket = null;
public TcpClient(int port,String ip) throws IOException {
this.socket = new Socket(ip,port);
}
2.start方法:首先创建InputStream和OutputStream,然后创建用于发送和接收的Scanner(因为TCP是面向字节流的,所以要用Scanner来接收数据),最后用PrintWrite来对发送的数据进行处理即可。
public void start(){
System.out.println("客户端启动!");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner sendFile = new Scanner(System.in);
Scanner receiveFile = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
System.out.println("请输入:");
String request = sendFile.next();
writer.println(request);
writer.flush();
String spones = receiveFile.next();
System.out.println(spones);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.创建主类,调用方法。
package Tcp;
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 TcpClient {
private Socket socket = null;
public TcpClient(int port,String ip) throws IOException {
this.socket = new Socket(ip,port);
}
public void start(){
System.out.println("客户端启动!");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner sendFile = new Scanner(System.in);
Scanner receiveFile = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
System.out.println("请输入:");
String request = sendFile.next();
writer.println(request);
writer.flush();
String spones = receiveFile.next();
System.out.println(spones);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpClient tcpClient = new TcpClient(9998,"127.0.0.1");
tcpClient.start();
}
}
测试效果: