什么是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();
}
}
}