什么是Socket?
所谓Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过“套接字”向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示。它主要包括以下两个协议:
TCP (Transmission Control Protocol 传输控制协议):传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP (User Datagram Protocl 用户数据报协议):用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
详细解说如下:
TCP传输和UDP不一样,TCP传输是流式的,必须先建立连接,然后数据流沿已连接的线路(虚电路)传输。因此TCP的数据流不会像UDP数据报一样,每个数据报都要包含目标地址和端口,因为每个数据报要单独路由。TCP传输则只需要在建立连接时指定目标地址和端口就可以了。
形象的讲,TCP就像打电话,UDP就像发电报。宏观上来看UDP是不分客户端和服务端的。通信双方是平等的。微观上来讲只相对一个报文,发送端是客户端,监听端是服务端。发送端把数据报发给路由器就像把电报发给了邮局,后面的事情就是发送者无法控制,也无从知晓的了。所以说是不可靠的,可能会出现报文丢失而无从知晓。就像每张电报都要有收件人一样,每个数据报都要有目的地址和端口。
而TCP每次连接都是分客户端和服务端的。连接的发起者(相当与拨号打电话的人)是客户端,监听者(相当于在电话边等着接电话的人)是服务端。发起者指定要连接的服务器地址和端口(相当于拨号),监听者通过和发起者三次握手建立连接(相当于听到电话响去接电话)。建立连接后双方可以互相发送和接受数据(打电话)。
Java如何操作Socket?
值得一提的是,Java分别为TCP和UDP提供了相应的类,TCP是java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用起来很方便!UDP是java.net.DatagramSocket.
127.0.0.1是回路地址,用于测试,相当于localhost本机地址,没有网卡,不设DNS都可以访问,端口地址在0~65535之间,其中0~1023之间的端口是用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口.
Socket通信模型如下:
如果大家对Java Socket编程还有模糊的地方抓紧温习(javascript:void(0)),本文不在此赘述,下面我们以最常用的TCP协议举例:
服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
客户端,使用Java socket通信对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
TCP网络连接模型:
Android客户端程序代分析:
[java] view plain copy
1. UploadActivity.java
2.
3. package com.android.upload;
4. import java.io.File;
5. import java.io.OutputStream;
6. import java.io.PushbackInputStream;
7. import java.io.RandomAccessFile;
8. import java.net.Socket;
9.
10. import android.app.Activity;
11. import android.os.Bundle;
12. import android.os.Environment;
13. import android.os.Handler;
14. import android.os.Message;
15. import android.view.View;
16. import android.view.View.OnClickListener;
17. import android.widget.Button;
18. import android.widget.EditText;
19. import android.widget.ProgressBar;
20. import android.widget.TextView;
21. import android.widget.Toast;
22.
23. import com.android.service.UploadLogService;
24. import com.android.socket.utils.StreamTool;
25.
26.
27. public class UploadActivity extends Activity {
28. private EditText filenameText;
29. private TextView resulView;
30. private ProgressBar uploadbar;
31. private UploadLogService logService;
32. private boolean start=true;
33. private Handler handler = new Handler(){
34. @Override
35. public void handleMessage(Message msg) {
36. int length = msg.getData().getInt("size");
37. uploadbar.setProgress(length);
38. float num = (float)uploadbar.getProgress()/(float)uploadbar.getMax();
39. int result = (int)(num * 100);
40. "%");
41. if(uploadbar.getProgress()==uploadbar.getMax()){
42. this, R.string.success, 1).show();
43. }
44. }
45. };
46.
47. @Override
48. public void onCreate(Bundle savedInstanceState) {
49. super.onCreate(savedInstanceState);
50. setContentView(R.layout.main);
51.
52. new UploadLogService(this);
53. this.findViewById(R.id.filename);
54. this.findViewById(R.id.uploadbar);
55. this.findViewById(R.id.result);
56. this.findViewById(R.id.button);
57. this.findViewById(R.id.stop);
58. new OnClickListener() {
59.
60. @Override
61. public void onClick(View v) {
62. false;
63.
64. }
65. });
66. new View.OnClickListener() {
67. @Override
68. public void onClick(View v) {
69. true;
70. String filename = filenameText.getText().toString();
71. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
72. new File(Environment.getExternalStorageDirectory(), filename);
73. if(uploadFile.exists()){
74. uploadFile(uploadFile);
75. else{
76. this, R.string.filenotexsit, 1).show();
77. }
78. else{
79. this, R.string.sdcarderror, 1).show();
80. }
81. }
82. });
83. }
84. /**
85. * 上传文件
86. * @param uploadFile
87. */
88. private void uploadFile(final File uploadFile) {
89. new Thread(new Runnable() {
90. @Override
91. public void run() {
92. try {
93. int)uploadFile.length());
94. String souceid = logService.getBindId(uploadFile);
95. "Content-Length="+ uploadFile.length() + ";filename="+ uploadFile.getName() + ";sourceid="+
96. null? "" : souceid)+"\r\n";
97. new Socket("192.168.1.78",7878);
98. OutputStream outStream = socket.getOutputStream();
99. outStream.write(head.getBytes());
100.
101. new PushbackInputStream(socket.getInputStream());
102. String response = StreamTool.readLine(inStream);
103. ";");
104. 0].substring(items[0].indexOf("=")+1);
105. 1].substring(items[1].indexOf("=")+1);
106. if(souceid==null){//代表原来没有上传过此文件,往数据库添加一条绑定记录
107. logService.save(responseid, uploadFile);
108. }
109. new RandomAccessFile(uploadFile, "r");
110. fileOutStream.seek(Integer.valueOf(position));
111. byte[] buffer = new byte[1024];
112. int len = -1;
113. int length = Integer.valueOf(position);
114. while(start&&(len = fileOutStream.read(buffer)) != -1){
115. 0, len);
116. length += len;
117. new Message();
118. "size", length);
119. handler.sendMessage(msg);
120. }
121. fileOutStream.close();
122. outStream.close();
123. inStream.close();
124. socket.close();
125. if(length==uploadFile.length()) logService.delete(uploadFile);
126. catch (Exception e) {
127. e.printStackTrace();
128. }
129. }
130. }).start();
131. }
132. }
133. StreamTool.java
134.
135. package com.android.socket.utils;
136.
137. import java.io.ByteArrayOutputStream;
138. import java.io.File;
139. import java.io.FileOutputStream;
140. import java.io.IOException;
141. import java.io.InputStream;
142. import java.io.PushbackInputStream;
143.
144. public class StreamTool {
145.
146. public static void save(File file, byte[] data) throws Exception {
147. new FileOutputStream(file);
148. outStream.write(data);
149. outStream.close();
150. }
151.
152. public static String readLine(PushbackInputStream in) throws IOException {
153. char buf[] = new char[128];
154. int room = buf.length;
155. int offset = 0;
156. int c;
157. loop: while (true) {
158. switch (c = in.read()) {
159. case -1:
160. case '\n':
161. break loop;
162. case '\r':
163. int c2 = in.read();
164. if ((c2 != '\n') && (c2 != -1)) in.unread(c2);
165. break loop;
166. default:
167. if (--room < 0) {
168. char[] lineBuffer = buf;
169. new char[offset + 128];
170. 1;
171. 0, buf, 0, offset);
172.
173. }
174. char) c;
175. break;
176. }
177. }
178. if ((c == -1) && (offset == 0)) return null;
179. return String.copyValueOf(buf, 0, offset);
180. }
181.
182. /**
183. * 读取流
184. * @param inStream
185. * @return 字节数组
186. * @throws Exception
187. */
188. public static byte[] readStream(InputStream inStream) throws Exception{
189. new ByteArrayOutputStream();
190. byte[] buffer = new byte[1024];
191. int len = -1;
192. while( (len=inStream.read(buffer)) != -1){
193. 0, len);
194. }
195. outSteam.close();
196. inStream.close();
197. return outSteam.toByteArray();
198. }
199. }
200.
201. UploadLogService.java
202.
203. package com.android.service;
204.
205. import java.io.File;
206.
207. import android.content.Context;
208. import android.database.Cursor;
209. import android.database.sqlite.SQLiteDatabase;
210.
211. public class UploadLogService {
212. private DBOpenHelper dbOpenHelper;
213.
214. public UploadLogService(Context context){
215. this.dbOpenHelper = new DBOpenHelper(context);
216. }
217.
218. public void save(String sourceid, File uploadFile){
219. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
220. "insert into uploadlog(uploadfilepath, sourceid) values(?,?)",
221. new Object[]{uploadFile.getAbsolutePath(),sourceid});
222. }
223.
224. public void delete(File uploadFile){
225. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
226. "delete from uploadlog where uploadfilepath=?", new Object[]{uploadFile.getAbsolutePath()});
227. }
228.
229. public String getBindId(File uploadFile){
230. SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
231. "select sourceid from uploadlog where uploadfilepath=?",
232. new String[]{uploadFile.getAbsolutePath()});
233. if(cursor.moveToFirst()){
234. return cursor.getString(0);
235. }
236. return null;
237. }
238. }
239.
240. DBOpenHelper.java
241.
242. package com.android.service;
243.
244. import android.content.Context;
245. import android.database.sqlite.SQLiteDatabase;
246. import android.database.sqlite.SQLiteOpenHelper;
247.
248. public class DBOpenHelper extends SQLiteOpenHelper {
249.
250. public DBOpenHelper(Context context) {
251. super(context, "upload.db", null, 1);
252. }
253.
254. @Override
255. public void onCreate(SQLiteDatabase db) {
256. "CREATE TABLE uploadlog (_id integer primary key autoincrement, uploadfilepath varchar(100), sourceid varchar(10))");
257. }
258.
259. @Override
260. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
261. "DROP TABLE IF EXISTS uploadlog");
262. onCreate(db);
263. }
264.
265. }
266.
267. main.xml
268.
269. <?xml version="1.0" encoding="utf-8"?>
270. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
271. "vertical"
272. "fill_parent"
273. "fill_parent"
274. >
275. <TextView
276. "fill_parent"
277. "wrap_content"
278. "@string/filename"
279. />
280.
281. <EditText
282. "fill_parent"
283. "wrap_content"
284. "022.jpg"
285. "@+id/filename"
286. />
287.
288. <Button
289. "wrap_content"
290. "wrap_content"
291. "@string/button"
292. "@+id/button"
293. />
294. <Button
295. "wrap_content"
296. "wrap_content"
297. "暂停"
298. "@+id/stop"
299. />
300. <ProgressBar
301. "fill_parent"
302. "20px"
303. "?android:attr/progressBarStyleHorizontal"
304. "@+id/uploadbar"
305. />
306. <TextView
307. "fill_parent"
308. "wrap_content"
309. "center"
310. "@+id/result"
311. />
312. </LinearLayout>
313.
314. AndroidManifest.xml
315.
316. <?xml version="1.0" encoding="utf-8"?>
317. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
318. package="com.android.upload"
319. "1"
320. "1.0" >
321.
322. "8" />
323.
324. <application
325. "@drawable/ic_launcher"
326. "@string/app_name" >
327. <activity
328. ".UploadActivity"
329. "@string/app_name" >
330. <intent-filter>
331. "android.intent.action.MAIN" />
332.
333. "android.intent.category.LAUNCHER" />
334. </intent-filter>
335. </activity>
336. </application>
337. <!-- 访问网络的权限 -->
338. "android.permission.INTERNET"/>
339. <!-- 在SDCard中创建与删除文件权限 -->
340. "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
341. <!-- 往SDCard写入数据权限 -->
342. "android.permission.WRITE_EXTERNAL_STORAGE"/>
343. </manifest>
Java服务端:
[java] view plain copy
1. SocketServer.javapackage com.android.socket.server;
2.
3. import java.io.File;
4. import java.io.FileInputStream;
5. import java.io.FileOutputStream;
6. import java.io.IOException;
7. import java.io.OutputStream;
8. import java.io.PushbackInputStream;
9. import java.io.RandomAccessFile;
10. import java.net.ServerSocket;
11. import java.net.Socket;
12. import java.text.SimpleDateFormat;
13. import java.util.Date;
14. import java.util.HashMap;
15. import java.util.Map;
16. import java.util.Properties;
17. import java.util.concurrent.ExecutorService;
18. import java.util.concurrent.Executors;
19.
20. import com.android.socket.utils.StreamTool;
21.
22. public class SocketServer {
23. private String uploadPath="D:/uploadFile/";
24. private ExecutorService executorService;// 线程池
25. private ServerSocket ss = null;
26. private int port;// 监听端口
27. private boolean quit;// 是否退出
28. private Map<Long, FileLog> datas = new HashMap<Long, FileLog>();// 存放断点数据,最好改为数据库存放
29.
30. public SocketServer(int port) {
31. this.port = port;
32. // 初始化线程池
33. executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
34. 50);
35. }
36.
37. // 启动服务
38. public void start() throws Exception {
39. new ServerSocket(port);
40. while (!quit) {
41. // 接受客户端的请求
42. // 为支持多用户并发访问,采用线程池管理每一个用户的连接请求
43. new SocketTask(socket));// 启动一个线程来处理请求
44. }
45. }
46.
47. // 退出
48. public void quit() {
49. this.quit = true;
50. try {
51. ss.close();
52. catch (IOException e) {
53. e.printStackTrace();
54. }
55. }
56.
57. public static void main(String[] args) throws Exception {
58. new SocketServer(7878);
59. server.start();
60. }
61.
62. private class SocketTask implements Runnable {
63. private Socket socket;
64.
65. public SocketTask(Socket socket) {
66. this.socket = socket;
67. }
68.
69. @Override
70. public void run() {
71. try {
72. "accepted connenction from "
73. " @ " + socket.getPort());
74. new PushbackInputStream(
75. socket.getInputStream());
76. // 得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
77. // 如果用户初次上传文件,sourceid的值为空。
78. String head = StreamTool.readLine(inStream);
79. System.out.println(head);
80. if (head != null) {
81. // 下面从协议数据中读取各种参数值
82. ";");
83. 0].substring(items[0].indexOf("=") + 1);
84. 1].substring(items[1].indexOf("=") + 1);
85. 2].substring(items[2].indexOf("=") + 1);
86. Long id = System.currentTimeMillis();
87. null;
88. if (null != sourceid && !"".equals(sourceid)) {
89. id = Long.valueOf(sourceid);
90. //查找上传的文件是否存在上传记录
91. }
92. null;
93. int position = 0;
94. if(log==null){//如果上传的文件不存在上传记录,为文件添加跟踪记录
95. new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date());
96. new File(uploadPath+ path);
97. if(!dir.exists()) dir.mkdirs();
98. new File(dir, filename);
99. if(file.exists()){//如果上传的文件发生重名,然后进行改名
100. 0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
101. new File(dir, filename);
102. }
103. save(id, file);
104. else{// 如果上传的文件存在上传记录,读取上次的断点位置
105. new File(log.getPath());//从上传记录中得到文件的路径
106. if(file.exists()){
107. new File(file.getParentFile(), file.getName()+".log");
108. if(logFile.exists()){
109. new Properties();
110. new FileInputStream(logFile));
111. "length"));//读取断点位置
112. }
113. }
114. }
115.
116. OutputStream outStream = socket.getOutputStream();
117. "sourceid="+ id+ ";position="+ position+ "\r\n";
118. //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=0
119. //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
120. outStream.write(response.getBytes());
121.
122. new RandomAccessFile(file, "rwd");
123. if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度
124. //移动文件指定的位置开始写入数据
125. byte[] buffer = new byte[1024];
126. int len = -1;
127. int length = position;
128. while( (len=inStream.read(buffer)) != -1){//从输入流中读取数据写入到文件中
129. 0, len);
130. length += len;
131. new Properties();
132. "length", String.valueOf(length));
133. new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
134. null);//实时记录文件的最后保存位置
135. logFile.close();
136. }
137. if(length==fileOutStream.length()) delete(id);
138. fileOutStream.close();
139. inStream.close();
140. outStream.close();
141. null;
142. }
143. catch (Exception e) {
144. e.printStackTrace();
145. finally {
146. try {
147. if(socket != null && !socket.isClosed()) socket.close();
148. catch (IOException e) {}
149. }
150. }
151.
152. }
153.
154. public FileLog find(Long sourceid) {
155. return datas.get(sourceid);
156. }
157.
158. // 保存上传记录
159. public void save(Long id, File saveFile) {
160. // 日后可以改成通过数据库存放
161. new FileLog(id, saveFile.getAbsolutePath()));
162. }
163.
164. // 当文件上传完毕,删除记录
165. public void delete(long sourceid) {
166. if (datas.containsKey(sourceid))
167. datas.remove(sourceid);
168. }
169.
170. private class FileLog {
171. private Long id;
172. private String path;
173.
174. public FileLog(Long id, String path) {
175. super();
176. this.id = id;
177. this.path = path;
178. }
179.
180. public Long getId() {
181. return id;
182. }
183.
184. public void setId(Long id) {
185. this.id = id;
186. }
187.
188. public String getPath() {
189. return path;
190. }
191.
192. public void setPath(String path) {
193. this.path = path;
194. }
195.
196. }
197. }
198. ServerWindow.javapackage com.android.socket.server;
199.
200. import java.awt.BorderLayout;
201. import java.awt.Frame;
202. import java.awt.Label;
203. import java.awt.event.WindowEvent;
204. import java.awt.event.WindowListener;
205.
206. public class ServerWindow extends Frame{
207. private SocketServer server;
208. private Label label;
209.
210. public ServerWindow(String title){
211. super(title);
212. new SocketServer(7878);
213. new Label();
214. add(label, BorderLayout.PAGE_START);
215. "服务器已经启动");
216. this.addWindowListener(new WindowListener() {
217. @Override
218. public void windowOpened(WindowEvent e) {
219. new Thread(new Runnable() {
220. @Override
221. public void run() {
222. try {
223. server.start();
224. catch (Exception e) {
225. e.printStackTrace();
226. }
227. }
228. }).start();
229. }
230.
231. @Override
232. public void windowIconified(WindowEvent e) {
233. }
234.
235. @Override
236. public void windowDeiconified(WindowEvent e) {
237. }
238.
239. @Override
240. public void windowDeactivated(WindowEvent e) {
241. }
242.
243. @Override
244. public void windowClosing(WindowEvent e) {
245. server.quit();
246. 0);
247. }
248.
249. @Override
250. public void windowClosed(WindowEvent e) {
251. }
252.
253. @Override
254. public void windowActivated(WindowEvent e) {
255. }
256. });
257. }
258. /**
259. * @param args
260. */
261. public static void main(String[] args) {
262. new ServerWindow("文件上传服务端");
263. 300, 300);
264. true);
265. }
266.
267. }
268. StreamTool.javapackage com.android.socket.utils;
269.
270. import java.io.ByteArrayOutputStream;
271. import java.io.File;
272. import java.io.FileOutputStream;
273. import java.io.IOException;
274. import java.io.InputStream;
275. import java.io.PushbackInputStream;
276.
277. public class StreamTool {
278.
279. public static void save(File file, byte[] data) throws Exception {
280. new FileOutputStream(file);
281. outStream.write(data);
282. outStream.close();
283. }
284.
285. public static String readLine(PushbackInputStream in) throws IOException {
286. char buf[] = new char[128];
287. int room = buf.length;
288. int offset = 0;
289. int c;
290. loop: while (true) {
291. switch (c = in.read()) {
292. case -1:
293. case '\n':
294. break loop;
295. case '\r':
296. int c2 = in.read();
297. if ((c2 != '\n') && (c2 != -1)) in.unread(c2);
298. break loop;
299. default:
300. if (--room < 0) {
301. char[] lineBuffer = buf;
302. new char[offset + 128];
303. 1;
304. 0, buf, 0, offset);
305.
306. }
307. char) c;
308. break;
309. }
310. }
311. if ((c == -1) && (offset == 0)) return null;
312. return String.copyValueOf(buf, 0, offset);
313. }
314.
315. /**
316. * 读取流
317. * @param inStream
318. * @return 字节数组
319. * @throws Exception
320. */
321. public static byte[] readStream(InputStream inStream) throws Exception{
322. new ByteArrayOutputStream();
323. byte[] buffer = new byte[1024];
324. int len = -1;
325. while( (len=inStream.read(buffer)) != -1){
326. 0, len);
327. }
328. outSteam.close();
329. inStream.close();
330. return outSteam.toByteArray();
331. }
332.
333. }
运行效果如下:
Android前端控制:
后台监控日志:
下载后的文件路径:
源码下载地址