Window与Android的socket通信_图片

 

这是我学习Android的第一个小项目,稍作记录,也希望大家看了能多帮我提出宝贵意见,共同交流进步。^_^

原型是基于C++的,只提供了最基本的socket连接功能。在此基础上,增加了以二进制流收发文件(.jpg)的方法,并且在client端显示收到的图片。

一. SOCKET连接

SERVER端:

 

 1 int CppServer::initialize()
 2 {
 3     int iResult = 0;
 4 
 5     ListenSocket = INVALID_SOCKET;
 6     ClientSocket = INVALID_SOCKET;
 7     result = NULL;
 8     rcvbuflen = DATA_BUFLEN - 1;
 9     sendbuflen = DATA_BUFLEN - 1;
10     quit = false;
11     initialized = false;
12 
13     memset(rcvbuf, 0x00, sizeof(rcvbuf));
14     memset(sendbuf, 0x00, sizeof(sendbuf));
15 
16     // Initialize Winsock
17     iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
18     if (iResult != 0) {
19         printf("WSAStartup failed with error: %d\n", iResult);
20         return -1;
21     }
22 
23     ZeroMemory(&hints, sizeof(hints));
24     hints.ai_family = AF_INET;
25     hints.ai_socktype = SOCK_STREAM;
26     hints.ai_protocol = IPPROTO_TCP;
27     hints.ai_flags = AI_PASSIVE;
28 
29     // Resolve the server address and port
30     iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
31     if ( iResult != 0 ) {
32         printf("getaddrinfo failed with error: %d\n", iResult);
33         WSACleanup();
34         return -1;
35     }
36 
37     // Create a SOCKET for connecting to server
38     ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
39     if (ListenSocket == INVALID_SOCKET) {
40         printf("socket failed with error: %ld\n", WSAGetLastError());
41         freeaddrinfo(result);
42         WSACleanup();
43         return -1;
44     }
45 
46     // Setup the TCP listening socket
47     iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
48     if (iResult == SOCKET_ERROR) {
49         printf("bind failed with error: %d\n", WSAGetLastError());
50         freeaddrinfo(result);
51         closesocket(ListenSocket);
52         WSACleanup();
53         return -1;
54     }
55 
56     freeaddrinfo(result);
57 
58     iResult = listen(ListenSocket, SOMAXCONN);
59     if (iResult == SOCKET_ERROR) {
60         printf("listen failed with error: %d\n", WSAGetLastError());
61         closesocket(ListenSocket);
62         WSACleanup();
63         return -1;
64     }
65 
66     // Accept a client socket
67     ClientSocket = accept(ListenSocket, NULL, NULL);
68     if (ClientSocket == INVALID_SOCKET) {
69         printf("accept failed with error: %d\n", WSAGetLastError());
70         closesocket(ListenSocket);
71         WSACleanup();
72         return -1;
73     }
74     printf("Client have connected.\n");
75 
76     initialized = true;
77     return 0;
78 }
SERVER: accept connection

 

这部分代码是C/C++ socket编程中流程化的部分,可以直接借鉴。其中的accept函数是阻塞的,当有client发来连接请求时,才往下执行。

CLIENT端:

 1 public class SocketClient extends Activity {
 2     private final String HOST = "10.0.2.2";    // Android虚拟机访问本地,不用127.0.0.1
 3     private final int PORT = 9527;        // 访问Server使用的端口号
 4     protected void onCreate(Bundle savedInstanceState) {
 5         new Thread() {
 6             public void run() {
 7                 Socket s = null;
 8                 try {
 9                     // Connect to server
10                     s = new Socket(HOST, PORT);
11                     // 更多处理:接收发送图片,显示图片
12                 } catch (UnknownHostException e) {
13                     e.printStackTrace();
14                 } catch (IOException e) {
15                     e.printStackTrace();
16                 } finally {
17                     try {
18                         s.close(); // 断开连接
19                     } catch (IOException e) {
20                         e.printStackTrace();
21                     }
22                 }
23             }
24         }.start();
25     }
26 
27     // 更多私有方法:提供显示图片用到的Handler,判断收发终了标志等
28 }
CLIENT: connect to server

以上是client端连接server socket的代码,核心只有

s = new Socket(HOST, PORT);

其他部分说明程序的结构。

※在Android 4 之后的版本中,不能在UI线程中使用连接socket这种阻塞式的方法,这将导致画面失去响应。因此这里在一个新启动的线程中发起socket连接

二. SERVER端向CLIENT端发送图片

SERVER端:

 1 int comm::sendData(char *file)
 2 {
 3     int iResult = 0;
 4 
 5     if(0 > _access(file, 0)) {
 6         // If resource file don't exist, send its name to client.
 7         iResult = send(ClientSocket, file, strlen(file), 0);
 8         printf("--File %s is not accessable, check please.\n", file);
 9         return -1;
10     } else {
11         FILE *rcFile = NULL;
12 
13         Beep(500, 350);
14         // Open the resource file.
15         rcFile = fopen(file, "rb+");
16         if(!rcFile) {
17             printf("Failed to open resource file: %s\n", file);
18             return -1;
19         }
20 
21         // Send resource file with 1024 byte per piece.
22         memset(sendbuf, 0x00, sizeof(sendbuf));
23         while(fread(sendbuf, sendbuflen, 1, rcFile) >= 0) {
24             if(SOCKET_ERROR == send(ClientSocket, sendbuf, sendbuflen, 0)) {
25                 closesocket(ClientSocket);
26                 WSACleanup();
27                 return -1;
28             }
29             if(feof(rcFile)) {
30                 break;
31             }
32         }
33         fclose(rcFile);
34 
35         // Send end mark to client.
36         memset(sendbuf, 0x00, sizeof(sendbuf));
37         strncpy_s(sendbuf, ENDMARK, sendbuflen);
38         if(SOCKET_ERROR == send(ClientSocket, sendbuf, sendbuflen, 0)) {
39             closesocket(ClientSocket);
40             WSACleanup();
41             return -1;
42         }
43 
44     }
45 
46     printf("--File %s have been sended successfully.\n", file);
47     return 0;
48 }
SERVER: send file

fread函数的原型是:

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
Params:
    buffer            // 存放从文件读取的内容,这里是char*类型
    size               // 读取内容元素的单位大小,单位是字节
    count            // 读取内容元素的数目(最大值)
                         // 个人理解前三个参数的关系为:buffer.size >= size * count
    stream          // 数据来源文件
Returns:
    // 实际读取内容元素的数目,返回值小于count说明到达文件末尾或者发生错误。我们的count==1,所以小于等于0时,为这种情况。

这个函数多次调用可以顺序读取stream指向的文件,直到文件末尾。使用feof函数判断是否到达文件结尾。

对于第一个参数void*到底选用什么类型,看到网上有朋友在这里定义了结构体,用结构体封装文件的每个片段,这种做法的好处是添加一些标记字段。这种做法虽然很标准,但是并不适合这个project。

在SERVER端有封装操作,CLIENT端必须有对应的解析操作。而我们都知道C/C++中,即使是基本数据类型,也有宽度和编译器有关系的,比如long,这无疑增加了用java解析数据包的难度,还要考虑到内存是否对齐等等问题,相当复杂且容易出错。因此这里使用简单的char来作为发送的buffer类型,它与java的byte类型具有相同且固定的宽度,方便数据解析(事实上这种情况已经简单到不需要解析了),对于一些必要的标记,比如终了标记OVER,单独发送一次,在client端做判断即可。

CLIENT端:

 1 // Receive picture
 2 private void rcvData() {
 3     try {
 4         // DataInputStream提供的read方法很适合读取byte[]类型数据
 5         sis = new DataInputStream(new BufferedInputStream(s.getInputStream()));
 6         // 打开Android内部存储空间的文件,必须使用openFileInput/openFileOutput
 7         // 可以直接返回FileInputStream/FileOutputStream,它们提供的read/write方法都对byte[]很友好
 8         fos = openFileOutput(tempFileName, MODE_PRIVATE);
 9 
10         while (true) {
11             Arrays.fill(buf,(byte) 0);
12             int readnum = sis.read(buf, 0, buflen);
13             if (ifEnd(buf) || 0>readnum) {
14                 break;
15             }
16             fos.write(buf);// write byte[] into file
17             fos.flush();
18         }
19     } catch (UnknownHostException e) {
20         e.printStackTrace();
21     } catch (IOException e) {
22         e.printStackTrace();
23     } finally {
24         try {
25             sis.close();
26             fos.close();
27         } catch (IOException e) {
28             e.printStackTrace();
29         }
30     }
31 
32     // getFilesDir() + "/" 可以用来读取程序内部存储空间的files目录
33     // 取到的路径为:/data/data/$appname/files
34     // ※最后是不带斜线的
35     bitmap = BitmapFactory.decodeFile(getFilesDir()+"/"+tempFileName, null);
36     handler.sendEmptyMessage(0);
37 }
38 
39 // Check received data for whether it's end mark.
40 private boolean ifEnd(byte[] data) {
41     String rcvData;
42     try {
43         // 这里把取到的byte[]按照ISO-8859-1转换为String
44         // byte[]每个单位1字节,String每个单位2字节
45         rcvData = new String(data, "ISO-8859-1");
46         // buffer可能不为空,在ENDMARK后还有其他数据,因此不用equals,改用startsWith
47         if(rcvData.startsWith(ENDMARK)) {
48             Log.i("DOCK", "ENDMARK found.");
49             return true;
50         }
51     } catch (UnsupportedEncodingException e) {
52         e.printStackTrace();
53     }
54     
55     return false;
56 }
57 
58 // 在socket线程之外使用handler,来改变activity上的内容
59 private Handler handler = new Handler() {
60     public void handleMessage(Message msg) {
61         // 一般的用法是switch XXX,我偷懒了~
62         super.handleMessage(msg);
63         showField.setImageBitmap(bitmap);
64     }
65 };
CLIENT: receive file

这部分是整个过程中耗时最多的部分,这次学到的东西都写在注释里面了。

需要交代的是Android的文件存放方式,请参考(感谢经验提供者)

http://www.open-open.com/lib/view/open1330957864280.html

三. CLIENT端向SERVER端发送图片

SERVER端:

 1 int CppServer::recvData(char *file)
 2 {
 3     int returnValue=0;
 4     int iResult = 1;
 5     FILE *rcvFile = NULL;
 6 
 7     if(0 == _access(file, 0)) {
 8         // If resource file already exist, print warning message.
 9         printf("----File %s have already existed. It will be clear.\n", file);
10     }
11 
12     rcvFile=fopen(file, "wb+");
13     if(!rcvFile) {
14         printf("Failed to open rcvFile. %s\n", file);
15         return -1;
16     }
17 
18     while(iResult > 0) {
19         memset(rcvbuf, 0x00, DATA_BUFLEN);
20         iResult = recv(ClientSocket, rcvbuf, rcvbuflen, 0);
21 
22         if(0 == strncmp(rcvbuf, "over", sizeof(rcvbuf))) {
23             //printf("Receive end signal from client.\n");
24             returnValue = 1;
25             break;
26         }
27         
28         if (iResult > 0) {
29             fwrite(rcvbuf, rcvbuflen, 1, rcvFile);
30         } else if (iResult == 0) {
31             printf("Connection closed response...\n");
32             returnValue = 0;
33             break;
34         } else {
35             printf("recv failed with error: %d\n", WSAGetLastError());
36             closesocket(ClientSocket);
37             WSACleanup();
38             returnValue = -1;
39             initialized = false;
40             break;
41         }
42     }
43     
44     fclose(rcvFile);
45     printf("--Data have been received successfully. Write data in file %s\n", file);
46     return returnValue;
47 }
SERVER: receive file

fwrite可以类比fread函数,参数与返回值的意义是类似的。

CLIENT端:

 1 // Send picture.
 2 private void rcvData() {
 3     int readNum = 0;
 4     fis = openFileInput(tempFileName);
 5     sos = new DataOutputStream(s.getOutputStream());
 6     while (true) {
 7         Arrays.fill(buf, (byte)0);
 8         readNum = fis.read(buf);
 9         if(0 > readNum) {
10             break;
11         }
12         sos.write(buf, 0, readNum);
13     }
14     sendEndMark(sos);
15 }
CLIENT: send file

整个client向server发送的过程,与server向client发送数据是类似的,只不过是read、write,inputstram、outputstream的变化,这里就不再分析。

 

posted on 2014-03-30 18:02  Dockyin  阅读(643)  评论(0编辑  收藏  举报