Socket
Java中客户端通常使用Socket
的构造器来连接到指定的服务端,
服务端的ServerSocket
的accept
方法接收到Socket
连接后也会产生一个Socket
对象,
此时就不存在客户端和服务端,他们就变成两个Socket
的交流。
ServerSocket的一些方法
方法 | 说明 |
---|---|
Socket accept() | 如果接收到客户端的Socket的连接请求,该方法返回一个与连接客户端Socket对应的Socket,否则该方法一直处于等待状态,线程也被阻塞。 |
ServerSocket(int port) | (构造器)指定端口port来创建一个ServerSocket |
ServerSocket(int port, int backlog) | (构造器)增加一个改变队列长度的参数backlog |
ServerSocket(int port, int backlog, InetAddress localAddr) | (构造器)在机器存在多个IP的情况下,通过localAddr指定IP地址 |
close() | 关闭服务器 |
Socket的一些方法
方法 | 说明 |
---|---|
Socket(InetAddress/String remoteAddress, int port) | (构造器)创建连接到指定IP和端口远程主机的Socket,默认使用本地主机的默认ip,系统动态分配的端口 |
Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort) | (构造器)创建连接到指定IP和端口远程主机的Socket,同时指定本地主机的ip和端口 |
Socket() | (构造器)未连接的Socket |
void connect(SocketAddress address) | 连接到指定地址 |
void connect(SocketAddress address, int timeoutInMilliseconds) | 连接到指定地址,若给定时间内没响应,则返回 |
void setSoTimeout(int timeoutInMilliseconds) | 给定一个读请求的阻塞时间,超时则抛异常InterruptedIOException |
boolean isConnected() | 如果已被连接,则返回true |
boolean closed() | 关闭Socket |
InputStream getInputStream() | 返回该Socket对象对应的输入流,让程序通过该输入流从Socket中读取数据 |
OutputStream getOutputStream() | 返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据 |
下面使用Socket制作一个简易的聊天室
服务端,这是一个纯java项目(我使用的是IntelliJ IDEA Community Edition 2021.2):
ServerThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
public class ServerThread implements Runnable{
//定义当前线程要处理的Socket
Socket s = null;
//当前线程要处理的Socket对应的输入流
BufferedReader br = null;
//该线程的构造器,参数为连接客户端的Socket
public ServerThread(Socket s) throws IOException {
this.s = s;
//取Socket的输入流
br = new BufferedReader(
new InputStreamReader(
s.getInputStream(), "utf-8"));
}
public void run() {
try {
String content = null;
//循环,不断从该Socket读消息
while((content = readFromClient()) != null) {
System.out.println(content);//这里做了打印操作,看下客户端的数据是否传过来了
//循环,给所有的已连接的Socket都传递该Socket的消息
for(Iterator<Socket> it = MyServer.socketArrayList.iterator(); it.hasNext(); ) {
Socket s = it.next();
try {
//取该Socket的输出流,赋值content + "\n"
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes(StandardCharsets.UTF_8));
}catch (SocketException e) {
e.printStackTrace();
it.remove();
System.out.println(MyServer.socketArrayList);
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
private String readFromClient() {
try {
return br.readLine();
}catch (IOException e) {
e.printStackTrace();
MyServer.socketArrayList.remove(s);
}
return null;
}
}
MyServer.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MyServer {
//定义保存所有的Socket的ArrayList
public static ArrayList<Socket> socketArrayList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(30000);
while(true) {
//此行代码会阻塞,一直等待别人连接
Socket s = ss.accept();
socketArrayList.add(s);
//每当客户端启动后启动一条线程为该客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
客户端,Android项目:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/input"
android:layout_width="280dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="发送" />
</LinearLayout>
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:background="#ffff"
android:textSize="14dp"
android:textColor="#f000" />
</LinearLayout>
ClientThread.java
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class ClientThread implements Runnable {
//客户端的IP,这里是我本地的IP地址,我打了马赛克了
private final static String IP = "***.**.**.*";
private Socket s;
//这个Handler读到服务端的信息后向UI线程发消息
private Handler handler;
//这个Handler本地点击发送信息后向服务器发消息
public Handler revHandler;
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler) {
this.handler = handler;
}
public void run() {
try {
s = new Socket(IP, 30000);
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
os = s.getOutputStream();
new Thread() {
public void run() {
String content = null;
try {
while((content = br.readLine()) != null) {
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}.start();
Looper.prepare();
revHandler = new Handler() {
public void handleMessage(Message msg) {
if(msg.what == 0x345) {
try {
os.write((msg.obj.toString() + "\r\n").getBytes("utf-8"));
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
Looper.loop();
}catch (SocketTimeoutException e) {
System.out.println("网络连接超时");
}catch (Exception e) {
e.printStackTrace();
}
}
}
MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
EditText input;
Button send;
TextView show;
Handler handler;
ClientThread clientThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = findViewById(R.id.input);
send = findViewById(R.id.send);
show = findViewById(R.id.show);
handler = new Handler() {
public void handleMessage(Message msg) {
if(msg.what == 0x123) {
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
new Thread(clientThread).start();
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
input.setText("");
}catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
然后AndroidManifest.xml还得加上联网权限
<uses-permission android:name="android.permission.INTERNET"/>
然后服务器,客户端一起运行
客户端手机每发送一条消息都会显示,服务端也能看到打印