初窥Socket:与自己聊次天
什么是Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定
但是,Socket所支持的协议种类不仅TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程
PS:虽然凑字数这种技能早就点满了,但关于更多Socket及TCP/IP相关概念,还请各位看官自行/先行了解,这里不再多做赘述
本次Demo预览
工具准备
- Eclipse(若你没有Eclipse也没事儿,后边告诉你用命令行编译运行!)
- AndroidStudio(若你本身就是用Eclipse开发安卓程序,那Eclipse就够了)
服务端
OK,话不多说,开干
首先在Eclipse新建一个Java项目,就叫SocketDemo吧
接下来咱们要监听是否有客户端发送连接请求,如果有,则连接并处理
SocketDemo.java:
public class SocketDemo {
/**
* 端口号 注意:0~1023为系统所保留端口号,选择端口号时应大于1023,具体随便你取
*/
public static int PORT = 2345;
public static void main(String[] args) {
try {
//serverSocket用于监听是否有客户端发送连接请求
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("服务启动...");
//serverSocket.accept():如果有客户端发送连接请求,
//则返回一个socket供处理与客户端的连接,否则一直阻塞监听
Socket socket = serverSocket.accept();
System.out.println("与客户端连接成功...");
//这个MySocket是啥呢?是一个对socket的封装,方便操作
MySocket mySocket = new MySocket(socket);
//由于MySocket继承于Thread,所以需要start()一下
//致于为啥要继承于Thread来封装socket,请看下方 MySocket类
mySocket.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
注释中的两个问题,很好理解,不多说,直接看看MySocket是怎么写的吧:
MySocket.java
public class MySocket extends Thread {
Socket mSocket;
BufferedWriter mWriter;
BufferedReader mReader;
public MySocket(Socket socket) {
this.mSocket = socket;
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), "utf-8"));
mReader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 向客户端发送消息
* msg 发送消息内容
**/
public void send(String msg) {
try {
// 客户端按行(readLine)读取消息,所以每条消息最后必须加换行符 \n,否则读取不到
mWriter.write(msg + "\n");
mWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* 不断读取来自客户端的消息,一旦收到消息,则自动回复
**/
@Override
public void run() {
super.run();
try {
String line;
//服务端按行读取消息
//不断按行读取,获得来自客户端的消息
while ((line = mReader.readLine()) != null) {
System.out.println("客户端消息:" + line);
//收到客户端消息后,自动回复
send("已经收到你发送的\"" + line + "\"");
}
mReader.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
看完MySocket之后豁然开朗,原来将读取客户端消息的操作是阻塞的,要放在子线程来做,所以继承于Thread会方便一点
那么至此,服务端的程序已经写完了
什么?你问怎么这么简单?!原因有两个:
- 这只是一个基础的Socket服务端程序,不用考虑那么多其他情况,自然几行代码就搞定了
- 没错,Socket就是这么简单!
接下来你会发现,客户端特么更简单!
客户端(Android)
第一步新建一个安卓项目,也叫SocketDemo吧,毕竟,凑字数这个技能我比较熟练
简单一点,布局中就一个按钮(id=btn_send),用来发送消息,初窥嘛,简单就是王道,布局代码就不上了
接下来看看MainActivity的代码:
不行,在看MainActivity之前还有一些话要交代清楚:
- 如果你将安卓程序跑在电脑的虚拟机上,则你访问的IP地址为:10.0.2.2(虚拟机只能通过这个IP访问电脑)
- 如果你将安卓程序跑在真机上,那么你需要在CMD中输入ipconfig获取到IPv4地址,并且确保手机和电脑在同一个网络下(连接了同一个WIFI)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connectServer();
findViewById(R.id.btn_send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendMsg("2333");
}
});
}
private Socket mSocket;
private BufferedWriter mWriter;
private BufferedReader mReader;
//这个IP上面解释过了噢,要理解一下
private static String IP = "10.0.2.2";
//切记端口号一定要和服务端保持一致!
private static int PORT = 2345;
private void connectServer() {
new Thread(new Runnable() {
@Override
public void run() {
try {
mSocket = new Socket(IP, PORT);
mWriter = new BufferedWriter(new OutputStreamWriter(
mSocket.getOutputStream(), "utf-8"));
mReader = new BufferedReader(new InputStreamReader(
mSocket.getInputStream(), "utf-8"));
Log.i(TAG, "连接服务端成功");
} catch (IOException e) {
Log.i(TAG, "连接服务端失败");
e.printStackTrace();
return;
}
try {
String line;
while ((line = mReader.readLine()) != null) {
Log.i(TAG, "服务端消息: " + line);
}
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "服务端:已停止服务");
}
}
}).start();
}
private void sendMsg(String msg) {
// 如果mSocket为null有可能两种情况:
// 1.还在尝试连接服务端
// 2.连接失败
if (mSocket == null){
Toast.makeText(this,"连接未完成或连接失败,无法发送消息!",Toast.LENGTH_SHORT).show();
return;
}
try {
//服务端是按行读取消息,所以每条消息最后必须加换行符 \n
mWriter.write(msg + "\n");
mWriter.flush();
} catch (IOException e) {
Toast.makeText(this,"发送失败:服务端已关闭服务!",Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
}
这就写完了客户端??对,这就写完了…那你别问为啥Socket咋就这么点内容,Socket本来也不是啥难点~
并且,这只是一个非常非常基础的Demo
OK,到这里就可以来跑一下程序试一试了
跑起来
跑服务端程序
- 如果你有Eclipse,那么直接在Eclipse内跑起来就行了!
如果很不巧,你没有Eclipse
- 新建本文章服务端部分的
SocketDemo.java
和MySocket.java
两个文件,并且放在同一个文件夹下,上面代码没有写出import包,不能直接copy进文件内用,文末我会放出所有源代码,到文末copy一下放在两个文件内就行了(当然你得确保你有JDK环境!虽然作为安卓狗,这是必要的,但还是提醒一下!) - 打开CMD,切换进入上述两个文件所在的目录
- 执行
就将程序跑起来了(ctrl+c退出程序)javac *.java java SocketDemo
- 新建本文章服务端部分的
- 注意事项:
- 在Eclipse内运行的程序,切记:如果修改内容后要重新启动程序,请先将正在运行的程序关闭,否则将一直占用端口!无法再以此端口再次启用一次程序!
- 如果用CMD运行的程序,提示编码错误,请将所有中文替换成英文,或者将两个.java文件内容转换成GBK编码(建议换成英文!英文好的哥们儿,上!)
跑客户端程序
直接跑安卓程序就行了!
在Eclipse跑服务端的图已经在文首放出,这里放一个CMD下跑服务端的图片:
注:不知为什么发送消息的时候,命令行及LogCat不会即时显示出内容,在我ctrl+c退出程序之后才会一次全出来,若有知道的朋友,还望指教!万分感谢!
改进一个不足
想一下,服务端程序只响应一个客户端,如果又有客户端发出连接请求,那岂不是无法响应了!
再想一下觉得不对,也就是我自己测试,哪来的第二个客户端发出连接请求
再再想一下,如果你改了一下安卓端的代码,又一次点了运行,那谁来响应你?!这样的话,因为修改安卓端代码,又得去把服务端的程序停了,再启动一下,多麻烦!
好吧,既然分析了确实有这个麻烦,那就把它解决掉:
public class SocketDemo {
public static int PORT = 2745;
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("服务启动...");
//写一个死循环,如果有一个客户端连接成功,那么继续让serverSocket.accept()阻塞住
//等待下一个客户端请求,这样不论有多少个客户端请求过来,都可以响应到,
//结束调试的时候再关闭服务端程序
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功...");
MySocket mySocket = new MySocket(socket);
mySocket.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
so easy~不解释了~
至此整个SocketDemo就完成了,对Socket的第一步已经迈出了,那么赶紧理解好,然后再深入Socket吧!
源码
- SocketDemo.java:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketDemo {
public static int PORT = 2745;
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("服务启动...");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功...");
MySocket mySocket = new MySocket(socket);
mySocket.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- MySocket.java:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class MySocket extends Thread {
Socket mSocket;
BufferedWriter mWriter;
BufferedReader mReader;
public MySocket(Socket socket) {
this.mSocket = socket;
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), "utf-8"));
mReader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String msg) {
try {
mWriter.write(msg + "\n");
mWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
super.run();
try {
String line;
while ((line = mReader.readLine()) != null) {
System.out.println("客户端消息:" + line);
send("收到:\"" + line + "\"");
}
mReader.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
- 客户端(安卓端)的我就不放了!