Java中的网络编程(TCP与UDP)

什么是Socket?

        Socket是一个抽象的概念,一个应用程序通过Socket建立一个远程连接,而Socket内部通过TCP/IP协议把数据传送至网络。

        简单的来说,Socket就是套接字,由IP地址和端口号(范围是0~65535)组成,端口号是由操作系统随机分配的,但是小于1024的端口号是特权端口,一般都是需要管理员权限,用户使用时可能会和系统中某个应用程序发送冲突,因此,用户一般能随意使用的端口号都要大于1024。通过端口号,Socket才能正确连接本机中的应用程序。

        那么如何来使用Socket来进行网络通信呢?其实需要两个Socket就可以了,一个Socket充当服务器端,一直处于监听指定端口状态,也就是ServerSocket,他的内容就是指定IP地址和指定端口号。另一个Socket充当客户端,必须主动连接服务器的IP地址和端口号,所以这个Socket的内容就是服务器的IP地址和服务器的端口号。这样客户端就和服务器实现了网络连接,从而能进行网络通信。

TCP编程

        在网络协议中TCP协议是一种可靠传输、面向连接的协议,而TCP编程是使用TCP协议实现的,主要使用的就是Socket类,通过Socket可以建立服务器端与客户端的连接,从而实现客户端与服务器端的交互,比如人机问答等程序。

一个实例:TCP实现人机聊天程序

服务器端

准备一个Map集合,用来问题和答案,如果客户端发出的提问,问题库中没有,则返回null,

首先创建一个ServerSocket对象,监听9999端口,通过死循环使服务器一直处于监听状态,使用

accept()方法接收来自客户端的连接,返回Socket对象。

然后通过getInputStream()方法,获取到来自该Socket对象发出的的提问,并通过字符输入流进行包装,提高读取效率。

最后将从map集合中找到的答案,封装到字符输出流中,通过getOutputStream()方法返回给Socket对象。实现一次问答流程。代码如下:

package com.fulian.tcp.demo02;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class ChatServer {
	public static void main(String[] args) {
		Map<String, String> chatMap = new HashMap<String, String>() {
			{
				put("你好", "你好呀");
				put("hi", "hi~");
				put("hello", "哈喽");
				put("吃了吗", "没呢,你呢");
				put("孤勇者", "爱你孤身走暗巷");
				put("有请潘周聃", "潘周聃,今年29岁,苏黎世理工大学.....");
				put("很高兴认识你", "我也是哦");
			}
		};

		try (ServerSocket server = new ServerSocket(9999)) {

			while (true) {
				// 发生客户端连接
				Socket client = server.accept();

				// 获取该客户端的IP地址
				String clientIP = client.getInetAddress().getHostAddress();

				try (BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
						BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))) {

					// 获取该客户端的提问
					String question = reader.readLine();
					System.out.println("【服务器】来自客户端" + clientIP + "的提问:" + question);

					// 获取该问题的答案
					String answer = chatMap.get(question);
					answer = answer == null ? "我不知道你在说什么" : answer;

					// 发送答案至客户端
					writer.write(answer);
					writer.flush();
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

客户端

首先创建一个Socket对象,传入服务器IP地址和端口号,通过Scanner对象获取用户提问。

然后通过getOutputStream输出流,将问题发往指定Socket对象,这里使用BufferedWriter进行包装。

发送完毕后,调用shutdownOutput(),暂停结束本次输出,可以理解为:客户端结束发送告知服务器:我发完了。

服务器端响应后,通过getOutputStream()输入流,返回给客户端答案这里使用BufferedReader进行包装。实现一次问答流程。代码如下:

package com.fulian.tcp.demo02;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);

		while (true) {
			// 创建Scoket,连接服务器
			try (Socket client = new Socket("192.168.254.169", 9999);
					BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
					BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()))) {

				// 获取控制台的输入(问题)
				String question = input.nextLine();
				if (question.equals("over")) {
					break;
				}

				// 发送问题至服务器
				writer.write(question);
				writer.flush();

				// 暂停结束本次输出
				client.shutdownOutput();

				// 来自服务器的答案
				String answer = reader.readLine();
				System.out.println("【客户端】来自服务器的回答:" + answer);

			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("Game over!");
	}
}

运行结果:

 UDP编程

          在网络协议中TCP协议是一种簿可靠传输、面无连接的协议,而UDP编程是使用UDP协议实现的,既然UDP协议是无连接的,那么就不能使用流进行网络通信,因此就只能使用数据包进行网络通信。使用UDP编程可以实现客户端与客户端之间的通信,比如一对一模拟聊天程序、一对多模拟对讲机程序等。

一个实例:发送端和接收端的聊天程序

发送端

首先,创建一个Socket监听8100端口(本机),这里的Socket是DatagramSocket,内部封装UDP协议。并提前创建了两个数据包DatagramPacket,sendPacket发送包:包括一个byte数组(缓冲区,用于存放发送到接收端的数据),和接收端的IP地址和端口号(8101),receivePacket接收包:包括一个空数组(缓冲区,用于接收到接收端的数据)。

发送数据时:通过setData()方法,将发送内容并转换成字符串存储到之前定义的空数组中,调用send()方法,实现数据的发送。

接收数据时:通过receive(receivePacket)方法,将接收到的内容传入之前receivePacket包定义的空数组,如果收到的是字符串,通过DatagramPacket返回的getOffset()和getLength()确定数据在缓冲区的起止位置,并转成字符串,实现数据的接收。实现一次聊天,代码如下:

package com.fulian.udp.demo02;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;

public class ChatA {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		// 客户端A监听8100端口
		try (DatagramSocket socket = new DatagramSocket(8100);){

			// 提前创建两个Packet数据包,分别用于发送和接收
			DatagramPacket sendPacket = new DatagramPacket(
					new byte[1024],1024, // 数据
					new InetSocketAddress("192.168.254.136", 8101)); // 目的地
			DatagramPacket receivePacket = new DatagramPacket(new byte[1024],1024);
			
			while(true) {
				// 发送
				System.out.print("你说:");
				String sendContent = input.nextLine();
				
				sendPacket.setData(sendContent.getBytes());
				socket.send(sendPacket);
				
				if(sendContent.equals("over")) {
					System.out.println("你已退出聊天!");
					break;
				}
				
				// 接收
				socket.receive(receivePacket);
				String receiveContent = new String(receivePacket.getData(),receivePacket.getOffset(),receivePacket.getLength());
				
				if(receiveContent.equals("over")) {
					System.out.println("对方已退出聊天!");
					break;
				}
				System.out.println("他说:" + receiveContent);
				
			}
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

接收端

整体思路和发送端一致,但是流程是先接收再发送。代码如下:

package com.fulian.udp.demo02;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;

public class ChatB {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		// 客户端B监听8101端口
		try (DatagramSocket socket = new DatagramSocket(8101);){

			// 提前创建两个Packet数据包,分别用于发送和接收
			DatagramPacket sendPacket = new DatagramPacket(
					new byte[1024],1024, // 数据
					new InetSocketAddress("192.168.254.136", 8100)); // 目的地
			DatagramPacket receivePacket = new DatagramPacket(new byte[1024],1024);
			
			while(true) {
				// 接收
				socket.receive(receivePacket);
				String receiveContent = new String(receivePacket.getData(),receivePacket.getOffset(),receivePacket.getLength());
				
				if(receiveContent.equals("over")) {
					System.out.println("对方已退出聊天!");
					break;
				}
				System.out.println("他说:" + receiveContent);
				
				// 发送
				System.out.print("你说:");
				String sendContent = input.nextLine();
				sendPacket.setData(sendContent.getBytes());
				socket.send(sendPacket);
				
				if(sendContent.equals("over")) {
					System.out.println("你已退出聊天!");
					break;
				}
			}
			
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

运行结果:

 

 另一个实例:多人对讲机

思路:实现多人对讲机功能,先准备一个map集合,组内成员的IP地址作为键,DatagramPacket发送包作为值,存放组内成员的IP地址和端口,可以将发送端定义成管理员,管理员先作为发送端进行发送,其他用户作为接收端进行监听,每个用户在发送和接收时处于一直循环状态,当听到某条指令比如“over”时,其他用户会进行判断,进行管理员权限转让,从而实现对讲功能。

代码如下:

首位管理员(先作为发送端)

package com.fulian.udp.demo03;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

public class ChatA {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		// 客户端A监听8100端口(首位管理员)
		try (DatagramSocket socket = new DatagramSocket(8100);){

			// 提前创建两个Packet数据包,分别用于发送和接收
			// map集合存放组内成员的IP地址和端口
			Map<String, DatagramPacket> map = new LinkedHashMap<String, DatagramPacket>(){
				{
					put("192.168.254.162",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.162", 8101)));
					put("192.168.254.168",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.168", 8102)));
					put("192.168.254.158",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.158", 8103)));
					put("192.168.254.120",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.120", 8104)));
					put("192.168.254.136",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.136", 8105)));
				}
			};
			
			DatagramPacket receivePacket = new DatagramPacket(new byte[1024],1024);
			
			Set<Entry<String, DatagramPacket>> set = map.entrySet();
			
			while(true) {
				while(true) {
					// 发送
					System.out.print("你说:");
					String sendContent = input.nextLine();
					
					for(Entry<String, DatagramPacket> e : set) {
						e.getValue().setData(sendContent.getBytes());
						socket.send(e.getValue());
					}
					// 当发送的内容是“over其他用户”时结束发言
					if(sendContent.equals("overB") || sendContent.equals("overC") ||
							sendContent.equals("overD") || sendContent.equals("overE") ) {
						System.out.println("结束发言!");
						break;
					}
				}
				
				
				// 接收
				while(true) {
					
					socket.receive(receivePacket);
					String receiveContent = new String(receivePacket.getData(),receivePacket.getOffset(),receivePacket.getLength());
					
					// 当监听到的内容是“overA”时发言
					if(receiveContent.endsWith("overA")) {
						System.out.println("开始发言");
						break;
					}
					System.out.println(receivePacket.getAddress() + ":" + receiveContent);
				}
				
			}
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

组内成员(先作为接收端)

package com.fulian.udp.demo03;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

public class ChatF {
	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		
		// 客户端F监听8105端口
		try (DatagramSocket socket = new DatagramSocket(8105);){

			// 提前创建两个Packet数据包,分别用于发送和接收
			// F
			Map<String, DatagramPacket> map = new LinkedHashMap<String, DatagramPacket>(){
				{
					put("192.168.254.162",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.162", 8101)));
					put("192.168.254.168",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.168", 8102)));
					put("192.168.254.158",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.158", 8103)));
					put("192.168.254.120",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.120", 8104)));
					put("192.168.254.136",new DatagramPacket(
							new byte[1024],1024,
							new InetSocketAddress("192.168.254.136", 8100)));
				}
			};
			
			DatagramPacket receivePacket = new DatagramPacket(new byte[1024],1024);
			
			Set<Entry<String, DatagramPacket>> set = map.entrySet();
			
			while(true) {
				
				// 接收
				while(true) {
					
					socket.receive(receivePacket);
					String receiveContent = new String(receivePacket.getData(),receivePacket.getOffset(),receivePacket.getLength());
					// 当监听到的内容是“overF”时发言
					if(receiveContent.endsWith("overF")) {
						System.out.println("开始发言");
						break;
					}
					System.out.println(receivePacket.getAddress() + ":" + receiveContent);
				}
				
				while(true) {
					// 发送
					System.out.print("你说:");
					String sendContent = input.nextLine();
					// A
					for(Entry<String, DatagramPacket> e : set) {
						e.getValue().setData(sendContent.getBytes());
						socket.send(e.getValue());
					}
					// 当发送的内容是“over其他用户”时结束发言
					if(sendContent.equals("overA") || sendContent.equals("overB") || sendContent.equals("overC") ||
							sendContent.equals("overD") || sendContent.equals("overE") ) {
						System.out.println("结束发言!");
						break;
					}
				}
				
			}
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仙草不加料

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值