InetAddress:用于描述网络中的计算机,是对域名、IP地址的封装
ServerSocket:服务端用的Socket,用于监听服务端的指定端口,当客户端连接到服务端的这个端口后,ServerSocket会为客户端创建一个Socket并分配给这个客户端,然后ServerSocket继续监听这个端口等待其他的客户端请求连接
Socket:客户端用的Socket以及服务端为每一个客户端连接请求建立的Socket
服务端:
public class SocketServer {
public static void main(String... args) throws IOException {
//1 创建一个服务端的ServerSocket,指定一个端口,监听此端口
ServerSocket serverSocket = new ServerSocket(12346);
//2 调用accept方法等待客户端连接
System.out.println("调用accept()等待客户端连接...");
Socket socket = serverSocket.accept();//accept()方法会一直阻塞,直到有客户端连接到服务端
System.out.println("socket建立成功...");
//3 连接后获取输入流,输出流
InputStream is = socket.getInputStream();//获取输入流,用于读取客户端发过来的信息
OutputStream os = socket.getOutputStream();//获取输出流,用于向客户端发送信息
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String data = null;
while ((data = br.readLine()) != null) {
//读取客户端发过来的信息
System.out.println("客户端发送过来的信息: " + data);
}
//向客户端发送信息
// ...
socket.shutdownInput();
socket.close();
}
}
客户端:
public class SocketClient {
public static void main(String ... args) throws IOException {
// 1 创建一个客户端Socket 指定服务器的ip地址和端口
Socket socket = new Socket("127.0.0.1",12346);
// 2 获取输出流,向服务器发送信息
OutputStream os = socket.getOutputStream();//向服务器写信息
PrintWriter pw = new PrintWriter(os);//将输出流包装成打印流
pw.write("Hello 服务器");//PrintWriter有缓冲队列,write的数据并不会立马发送出去,如果想立马发送需要调用flush()方法
pw.flush();
socket.shutdownOutput();//关闭输出流
socket.close();
}
}
以上就是简单的Socket通信模型,任何其他复杂的Socket通信框架都是基于这个最基础的Socket通信模型实现的,比如服务端实现多客户连接,高并发,客户端实现TCP心跳包机制等。
/**
* 简单聊天室的服务端
*/
public class ChartServer {
private ServerSocket server = null; //服务器的ServerSocket
private static final int PORT = 10065;
private List<Socket> mClients = new ArrayList<>();//保存所有的client
private ExecutorService mExec = null;
public static void main(String ... args){
new ChartServer();
}
public ChartServer(){
//开启服务
System.out.println("服务器运行中。。。");
try {
server = new ServerSocket(PORT);
//创建一个线程池
mExec = Executors.newCachedThreadPool();
Socket client = null;
while (true){
System.out.println("等待客户上门。。。");
client = server.accept();
System.out.println("有客户来了。。。 ,客户是: " + client.getInetAddress());
mClients.add(client);
mExec.execute(new Service(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}
class Service implements Runnable{
private Socket socket;
private BufferedReader br = null;
private String msg = "";
public Service(Socket socket){
this.socket = socket;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.sendMsg();
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendMsg(){
//会给客户端的消息
int num = mClients.size();
OutputStream os = null;//向服务器写信息
try {
os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);//将输出流包装成打印流
pw.write("你好 你是第"+ num + "个客户");
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
if(( msg = br.readLine())!=null){
System.out.println("客户端说:" + msg);
if("bye".equals(msg)){
//应用自己定义的协议
socket.close();
break;
}else{
sendMsg();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 简单聊天室的客户端,没有做心跳包机制
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Zero";
@BindView(R.id.tv_ip)
TextView tvIp;
@BindView(R.id.tv_show)
TextView tvShow;
@BindView(R.id.et_send)
EditText etSend;
//定义相关变量,完成初始化
private static final String HOST = "192.168.0.185";
// private static final String HOST = "169.254.177.122";
private static final int PORT = 10065;
private Socket socket = null;
private BufferedReader in = null;
private PrintWriter out = null;
private String content = "";
private StringBuilder sb = null;
private boolean writerFlag = false;
//这里引入了阻塞队列LinkedBlockingQueue作为生产者线程和消费者线程的缓冲队列,避免了生产者线程和消费者线程同步造成的复杂操作。
private LinkedBlockingQueue<String> msgs = new LinkedBlockingQueue<>();
//定义一个handler对象,用来刷新界面
public Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
sb.append(content);
tvShow.setText(sb.toString());
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvIp.setText(NetworkUtils.getIPAddress(this));
sb = new StringBuilder();
//网络操作不能放在UI主线程 4.0
new Thread(){
public void run(){
try {
//和服务器连接
socket = new Socket(HOST, PORT);
//获取输入流,读取服务器发送过来的信息
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取输出流 向服务器写数据
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
new Thread(readRunnable).start();
}
private Runnable readRunnable = new Runnable() {
@Override
public void run() {
while (true){
if(socket ==null)continue;
if(socket.isConnected() && !socket.isInputShutdown()){
try {
if((content = in.readLine()) !=null){
content += "\n";
handler.sendEmptyMessage(0x123);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
};
private Runnable writeRunnable = new Runnable() {
@Override
public void run() {
while (true){
if(socket ==null)break;
if(socket.isConnected() && !socket.isOutputShutdown()){
try {
out.println(msgs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
out.flush();
}
}
}
};
@OnClick(R.id.btn_send)
public void onViewClicked() {
if(!writerFlag){
new Thread(writeRunnable).start();
}
writerFlag = true;
String msg = etSend.getText().toString();
try {
msgs.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里引入了阻塞队列LinkedBlockingQueue作为生产者线程和消费者线程的缓冲队列,避免了生产者线程和消费者线程同步造成的复杂操作。
新增心跳包机制是为了实现TCP长连接。
TCP长连接被断开主要有几个原因:
其中,TCP长连接所在进程被操作系统杀死和NAT超时是两个完全不同的原因,前者是手机操作系统对于后台进程会杀死的机制,后者是网络运营商(比如移动,电信等)针对NAT超时的处理机制。
腾讯的Mars
美团的Shark长连接框架
一般互联网大厂都有自己的长连接框架
更多相关内容可以参考:
Android架构之长连接技术
android socket通信框架_阿里P8典藏:Java多线程与Socket实战微服务框架笔记
Android-OkSocket一个Android轻量级Socket通讯框架
青春互撩——详解基于Socket通信的聊天软件开发(附项目源码)