Java:网络编程
文章目录
1. 基础的通信架构
基本的通信架构有2种形式:C/S架构、B/S架构,其中C/S架构即客户端/服务器架构,用户在使用时,需要额外安装客户端,当后台版本更新时,客户端也可能需要更新才能使用;B/S架构即浏览器/服务器架构,用户只需要在浏览器上输入对应的网址便可以使用。
网络通信的关键三要素:ip地址、端口号、协议。其中ip地址也就是设备在网络中的地址,是唯一标识;端口号是应用程序在设备中的唯一标识;协议是连接和数据在网络中传输的规则。
1-1. IP地址
ip地址:全称为“互联网协议地址”,是分配给上网设备的唯一标识。ip地址有两种形式:IPv4、IPv6;其中IPv4采用4字节(32位)来记录这个地址:
IPv6共128位,分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号隔开;
电脑上在cmd命令上输入:ipconfig即可查询当前电脑的ipv4、ipv6地址。
在Java中获取到IP数据(InetAddress)
package Scoket_study;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class ScoketStudy {
public static void main(String[] args) throws UnknownHostException {
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost.getHostName());
// 获取到本机电脑名
System.out.println(localHost.getHostAddress());
// 获取到本机的ip地址
InetAddress localHost2 = InetAddress.getByName("www.bilibili.com");
System.out.println(localHost2.getHostAddress());
}
}
1-2. 端口号
被规定为一个16位的二进制,范围为0~65535。
周知端口:0~1023,被预定义的知名应用占用(http占用80,ftp占用21);
注册端口:1024~49151,分配给用户进程或某些应用程序;
动态端口:49152~65535,不固定分配给某种进程,而是动态分配的。
自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序端口号相同(会出现端口号被占用的情况)。
1-3. 协议
TCP/IP网络模型:实际上国际标准。分为应用层、传输层、网络层、数据链路层和物理层。在应用层上,比如http、ftp、smtp;传输层比如UDP、TCP;网络层,比如ip;数据链路层和物理层,比如比特流
UDP:全称 User Datagram Protocol,也就是用户数据协议,不事先建立连接 ,数据按包发送,包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(最大为64kb)等;发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认。这种方式通信效率高,可以用在语音通话、视频直播等。
TCP:全称 Transmission Control Protocol,也就是传输控制协议,通过三次握手建立连接,传输数据进行确认,四次握手断开连接。
三次握手建立连接,客户端向服务器端发送请求,如果服务器端能接受到消息,会向客户端返回一个响应,以此表示服务器端能接收到客户端发送的请求,之后客户端再发送一个请求给服务器端,以此表示客服端能接收到服务器端返回的响应,从而建立起一个连接。
四次握手断开连接,客户端发送断开连接请求给服务器端,服务器收到请求之后,返回一个响应:稍等(因为此时服务器可能还有数据没有处理完);上一步执行完之后,会再返回一个响应:确认断开(用来确认客服端是否确认断开),客户端收到这个响应之后,发出正式确认断开连接。
2. UDP通信
在Java中提供了一个java.net.DatagreamSocket来实现UDP通信。
客户端发送数据参考代码如下:
package Scoket_study;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class Client1 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
// 创建客服端对象
byte[] send_data = "见到你真高兴。。".getBytes();
DatagramPacket packet = new DatagramPacket(send_data,
send_data.length, InetAddress.getLocalHost(),6666);
// 第一参数表示发送的数据字节码数组
// 第二个参数表示发送的数据长度
// 第三个参数表示发送到的服务器的ip地址
// 第四个参数表示端口号
// 创建数据包对象
socket.send(packet);
// 发送数据到服务器端
System.out.println("客户端发送数据成功!");
socket.close();
}
}
服务器端发送数据参考代码如下:
package Scoket_study;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server1 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(6666);
// 创建服务器端,端口号为 6666
byte[] recev = new byte[1024*64];// 64kb
DatagramPacket packet = new DatagramPacket(recev, recev.length);
socket.receive(packet);
int len = packet.getLength();
// 获取本次接收到的数据长度
System.out.println("服务器端接收到的数据为:"+new String(recev,0,len));
// 接收多少,输出多少
socket.close();
}
}
首先需要先执行服务器端的代码,然后再执行客户端的代码,运行结果如下:
如果想让上述两端可以反复接收消息,此时需要用到死循环,参考代码如下:
package Scoket_study.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Client1 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
// 创建客服端对象
Scanner scanner = new Scanner(System.in);
String msg;
DatagramPacket packet;
while(true){
System.out.print("请输入:");
msg = scanner.nextLine();
if("quit".equals(msg)){
System.out.println("客户端已关闭,欢迎下次使用!");
socket.close();
break;
}
byte[] send_data = msg.getBytes();
packet = new DatagramPacket(send_data,
send_data.length, InetAddress.getLocalHost(),6666);
// 第一参数表示发送的数据字节码数组
// 第二个参数表示发送的数据长度
// 第三个参数表示发送到的服务器的ip地址
// 第四个参数表示端口号
// 创建数据包对象
socket.send(packet);
// 发送数据到服务器端
System.out.println("客户端发送数据成功!");
}
}
}
package Scoket_study.udp;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server1 {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(6666);
// 创建服务器端,端口号为 6666
byte[] recev = new byte[1024*64];// 64kb
DatagramPacket packet = new DatagramPacket(recev, recev.length);
while(true){
socket.receive(packet);
int len = packet.getLength();
// 获取本次接收到的数据长度
System.out.println("服务器端接收到的数据为:"+new String(recev,0,len));
// 接收多少,输出多少
}
}
}
3. TCP通信
在Java中提供了一个java.net.Socket类来实现TCP通信。
客户端的开发参考代码如下:
package Scoket_study.tcp;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
// 参数为服务器端的ip地址和端口号
OutputStream ops = socket.getOutputStream();
DataOutputStream dops = new DataOutputStream(ops);
// 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
dops.writeUTF("你好!");
dops.close();
socket.close();
}
}
服务器端的开发参考代码如下:
package Scoket_study.tcp;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
// 服务端
Socket socket = serverSocket.accept();
InputStream ips = socket.getInputStream();
DataInputStream dips = new DataInputStream(ips);
// 客户端使用的是数据输出流,因此这里需要使用数据输入流
String s = dips.readUTF();
System.out.println("客户端发送的消息为:"+s);
System.out.println(socket.getRemoteSocketAddress());
// 获取客户端的一些信息
dips.close();
socket.close();
}
}
运行结果:
如果想要实现客户端能多发,服务器端能多收,此时需要使用到while死循环,参考代码如下:
package Scoket_study.tcp;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
// 服务端
System.out.println("xxx服务端启动成功。。。。。。。。。。。");
Socket socket = serverSocket.accept();
InputStream ips = socket.getInputStream();
DataInputStream dips = new DataInputStream(ips);
// 客户端使用的是数据输出流,因此这里需要使用数据输入流
while(true){
try {
String s = dips.readUTF();
System.out.println("客户端发送的消息为:"+s);
} catch (IOException e) {
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了。。。。");
dips.close();
socket.close();
break;
}
}
// 获取客户端的一些信息
}
}
package Scoket_study.tcp;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
// 参数为服务器端的ip地址和端口号
OutputStream ops = socket.getOutputStream();
DataOutputStream dops = new DataOutputStream(ops);
// 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
Scanner scanner = new Scanner(System.in);
System.out.println("xxxx客户端。。。。。。。");
while(true){
System.out.print("输入:");
String msg = scanner.nextLine();
if("quit".equals(msg)){
dops.close();
socket.close();
System.out.println("退出成功!");
break;
}
dops.writeUTF(msg);
dops.flush();
// 需要刷新流
}
}
}
4. TCP通信支持多个客户端通信
上述代码仅仅只能接收到一个客户端的消息,无法接收到多个客户端消息。为此,需要用到多线程技术。上述客户端的代码不需要做修改,只需要修改服务器端的代码即可,参考代码如下:
package Scoket_study.tcp;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
// 服务端
System.out.println("xxx服务端启动成功。。。。。。。。。。。");
while(true){
Socket socket = serverSocket.accept();
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"上线了。。。。");
SocketThread thread = new SocketThread(socket);
thread.start();
}
}
}
class SocketThread extends Thread{
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream ips = socket.getInputStream();
DataInputStream dips = new DataInputStream(ips);
while(true){
try {
String msg = dips.readUTF();
System.out.println("接收到客户端:"+socket.getRemoteSocketAddress()+"的消息:"+msg);
} catch (IOException e) {
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
dips.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 使用TCP通信实现群聊功能
也就是一个客户端发送消息给服务器端,然后通过服务器端发送给其他客户端。在服务器端为了存储当前已经上线了客户端,这里需要用到集合来进行存储这些客户端。
package Scoket_study.tcp2;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
// 参数为服务器端的ip地址和端口号
OutputStream ops = socket.getOutputStream();
DataOutputStream dops = new DataOutputStream(ops);
// 使用数据输出流,当然也可以使用其他流,比如缓冲流、打印流
Scanner scanner = new Scanner(System.in);
System.out.println("xxxx客户端。。。。。。。");
new ClientThread(socket).start();
while(true){
String msg = scanner.nextLine();
if("quit".equals(msg)){
dops.close();
socket.close();
System.out.println("退出成功!");
break;
}
dops.writeUTF(msg);
dops.flush();
// 需要刷新流
}
}
}
// 客户端接收消息
class ClientThread extends Thread{
private Socket socket;
public ClientThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream ips = socket.getInputStream();
DataInputStream dips = new DataInputStream(ips);
while(true){
try {
String msg = dips.readUTF();
System.out.println("接收到"+socket.getRemoteSocketAddress()+"客户端的消息:"+msg);
} catch (IOException e) {
//System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
dips.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package Scoket_study.tcp2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class Server {
public static List<Socket> onLineSockets = new ArrayList<>();
// 使用静态变量
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
// 服务端
System.out.println("xxx服务端启动成功。。。。。。。。。。。");
while(true){
Socket socket = serverSocket.accept();
onLineSockets.add(socket);
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"上线了。。。。");
SocketThread thread = new SocketThread(socket);
thread.start();
}
}
}
class SocketThread extends Thread{
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream ips = socket.getInputStream();
DataInputStream dips = new DataInputStream(ips);
while(true){
try {
String msg = dips.readUTF();
System.out.println("接收到客户端:"+socket.getRemoteSocketAddress()+"的消息:"+msg);
sendMsgToAll(msg);
} catch (IOException e) {
System.out.println("客户端:"+socket.getRemoteSocketAddress()+"下线了");
Server.onLineSockets.remove(socket);
dips.close();
socket.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 发送消息给其他客户端
private void sendMsgToAll(String msg) throws IOException {
for (Socket onLineSocket : Server.onLineSockets) {
if(onLineSocket != socket){
OutputStream ops = onLineSocket.getOutputStream();
DataOutputStream dops = new DataOutputStream(ops);
dops.writeUTF(msg);
dops.flush();
}
}
}
}
运行结果:
如果想要在Idea上运行一个文件多次,需要把这个设置点上才行哈!
6. TCP 实现B/S架构
package Scoket_study.tcp_bs;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while(true){
Socket socket = serverSocket.accept();
new BsThread(socket).start();
}
}
}
class BsThread extends Thread{
private Socket socket;
public BsThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream ops = socket.getOutputStream();
PrintStream ps = new PrintStream(ops);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<div color='red' style='font-size:30px;font-family:楷体;'>爱我中华</div>");
ps.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
此时在浏览器上输入:127.0.0.1:8080,即可查看运行结果:
但是上述代码还存在一个问题,那就是每访问一下这个地址,就需要新创建一个线程类对象,一旦多了,肯定会影响到性能的,因此考虑使用线程池。
package Scoket_study.tcp_bs;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
ThreadPoolExecutor pool = new ThreadPoolExecutor(16*2,16*2,0, TimeUnit.SECONDS
,new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
while(true){
Socket socket = serverSocket.accept();
pool.execute(new BsRunnable(socket));
}
}
}
class BsRunnable implements Runnable{
private Socket socket;
public BsRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream ops = socket.getOutputStream();
PrintStream ps = new PrintStream(ops);
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=UTF-8");
ps.println();
ps.println("<div color='red' style='font-size:30px;font-family:楷体;'>爱我中华</div>");
ps.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
原文地址:https://blog.csdn.net/qq_45404396/article/details/144135310
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!