Socket通信
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。基于TCP/IP协议,建立稳定的点对点的通信。
特点:实时、快速、安全性高、占用系统资源多、效率低。
通常也称作"套接字",套接字是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。
“请求----响应”模式
客户端:在网络通讯中,第一次主动发起通讯的程序被称作客户端(client)程序。
服务器:第一次通讯中等待连接的程序被称作服务器端(server)程序。
这里模拟了一个聊天室的简单的聊天功能:
首先我们先准备一个关闭流的工具类CloseUtil,以后流的关闭都可以通过调用这个方法来实现:
public class CloseUtil {
public static void closeAll(Closeable... io){
for (Closeable temp : io) {
try {
if (null != temp){
temp.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
然后我们实现数据的接收和发送:
public class Send implements Runnable{
//控制台输入流
private BufferedReader console;
//管道输出流
private DataOutputStream dos;
//控制线程标识
private boolean isRunning = true;
//名称
private String name;
public Send() {
console = new BufferedReader(new InputStreamReader(System.in));
}
public Send(Socket client, String name){
this();
try {
dos = new DataOutputStream(client.getOutputStream());
this.name = name;
send(this.name);
} catch (IOException e) {
isRunning = false;
CloseUtil.closeAll(dos,console);
}
}
/**
* 1.从控制台接收数据
* @return
*/
private String getMsgFromConsole(){
try {
return console.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
/**
* 2.发送数据
*/
public void send(String msg){
try {
if (null != msg && !msg.equals("")){
dos.writeUTF(msg);
dos.flush();//强制刷新
}
} catch (IOException e) {
isRunning = false;
CloseUtil.closeAll(dos,console);
}
}
@Override
public void run() {
while (isRunning){
send(getMsgFromConsole());
}
}
}
public class Receive implements Runnable{
//输入流
private DataInputStream dis;
//线程标识
private boolean isRunning = true;
public Receive() {
}
public Receive(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
} catch (IOException e) {
isRunning = false;
CloseUtil.closeAll(dis);
}
}
/**
* 接收数据
* @return
*/
public String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
isRunning = false;
CloseUtil.closeAll(dis);
}
return msg;
}
@Override
public void run() {
//线程体
while (isRunning){
System.out.println(receive());
}
}
}
最后是我们模拟客户端和服务器的实现:
public class Server {
private List<MyChannel> all = new ArrayList<MyChannel>();
public static void main(String[] args) throws IOException {
new Server().start();
}
public void start() throws IOException {
ServerSocket server = new ServerSocket(9999);
while (true){
Socket client = server.accept();
MyChannel channel = new MyChannel(client);
all.add(channel);//统一管理
new Thread(channel).start();//一条道路
}
}
/**
* 一个客户端一条道路
* 1.输入流
* 2.输出流
* 3.接收数据
* 4.发送数据
*/
class MyChannel implements Runnable{
private DataInputStream dis;
private DataOutputStream dos;
private String name;
//线程标识
private boolean isRunning = true;
public MyChannel(Socket client) {
try {
dis = new DataInputStream(client.getInputStream());
dos = new DataOutputStream(client.getOutputStream());
this.name = dis.readUTF();
this.send("欢迎进入聊天室");
this.sendOthers(this.name + "进入了聊天室", true);
} catch (IOException e) {
CloseUtil.closeAll(dis,dos);
isRunning = false;
}
}
/**
* 读取数据
* @return
*/
private String receive(){
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
CloseUtil.closeAll(dis);
isRunning = false;
all.remove(this);//移除自身
}
return msg;
}
/**
* 发送数据
* @return
*/
private void send(String msg){
if (null==msg || msg.equals("")){
return;
}
try {
dos.writeUTF(msg);
dos.flush();
} catch (IOException e) {
CloseUtil.closeAll(dos);
isRunning = false;
all.remove(this);//移除自身
}
}
/**
* 发送给其他的客户端
*/
private void sendOthers(String msg, boolean sys){
//是否为私聊
if (msg.startsWith("@") && msg.indexOf(":")>-1){ //约定私聊以@开始
//获取name
String name = msg.substring(1, msg.indexOf(":"));
String content = msg.substring(msg.indexOf(":") + 1);
for (MyChannel other : all) {
if (other.name.equals(name)){
other.send(this.name + "对您悄悄的说:" + content);
}
}
} else {
//遍历容器
for (MyChannel other : all) {
if (other == this){
continue;
}
if (sys){
//系统消息
other.send("系统信息:" + msg);
} else {
//发送给其他客户端
other.send(this.name + "对所有人说:" + msg);
}
}
}
}
@Override
public void run() {
while (isRunning){
sendOthers(receive(), false);
}
}
}
}
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("请输入名称:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String name = br.readLine();
if (name.equals("")){
return;
}
Socket client = new Socket("localhost",9999);
new Thread(new Send(client, name)).start();//一条路径
new Thread(new Receive(client)).start();//一条路径
}
}
下面是我们的运行结果,先启动Server。然后启动Client,为第一个进程命名为a;然后再启动一个进程,命名为b;最后再启动一个进程并命名为c。在c进程中输入“大家好”,则a进程和b进程都可以收到这条消息,在c进程中输入“@a:小a同学,你好。”,则我们只能在a进程中收到这条消息。并且在b和c启动的时候,会有系统消息提示,b或者c进入聊天室。截图如下,依次为abc三个进程的截图:
以上就是模拟简单的群聊功能的实现。还可以在结束进程的时候添加系统消息提示某进程离开聊天室,只要对以上代码稍作修改即可。这里没有实现。
以上为尚学堂Java300集教学视频中裴新老师所教授的网络编程相关课程的笔记。