Echo Socket例子项目
? 這個例子提供如下:
????? 定義必要的配置sockets的參數的一個簡單的用戶接口。
???? service邏輯對于一個見到的echo服務重復這接收到的字節返回給發送者。
??? 模塊化原生代碼片段來對于Android的原生層方便socket編程。
?? 一個面向連接的socket通信例子。
?? 一個無連接的通信例子。
?? 一個本地的socket通信例子。
? 建立一個Echo的Android項目。
?
??? Abstractt Echo Activity:
?? 為了重復利用者普通的功能,將會創建一個抽象的activity類在定義這時間activity之前。使用Project Explorer 視圖,打開src目錄,旋轉com.apress.echo包,和選擇new-》class。設置名字為AbstractEchoActivity和點擊這完成按鈕。該新文件的內容如下:
package com.apress.echo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
/**
* Abstract echo activity object.
*
* @author Onur Cinar
*/
public abstract class AbstractEchoActivity extends Activity implements
OnClickListener {
/** Port number. */
protected EditText portEdit;
/** Server button. */
protected Button startButton;
/** Log scroll. */
protected ScrollView logScroll;
/** Log view. */
protected TextView logView;
/** Layout ID. */
private final int layoutID;
/**
* Constructor.
*
* @param layoutID
Communication
* layout ID.
*/
public AbstractEchoActivity(int layoutID) {
this.layoutID = layoutID;
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(layoutID);
portEdit = (EditText) findViewById(R.id.port_edit);
startButton = (Button) findViewById(R.id.start_button);
logScroll = (ScrollView) findViewById(R.id.log_scroll);
logView = (TextView) findViewById(R.id.log_view);
startButton.setOnClickListener(this);
}
public void onClick(View view) {
if (view == startButton) {
onStartButtonClicked();
}
}
/**
* On start button clicked.
*/
protected abstract void onStartButtonClicked();
/**
* Gets the port number as an integer.
*
* @return port number or null.
*/
protected Integer getPort() {
Integer port;
try {
port = Integer.valueOf(portEdit.getText().toString());
} catch (NumberFormatException e) {
port = null;
}
return port;
}
/**
* Logs the given message.
*
* @param message
* log message.
*/
protected void logMessage(final String message) {
runOnUiThread(new Runnable() {
public void run() {
logMessageDirect(message);
}
});
}
/**
* Logs given message directly.
*
* @param message
* log message.
*/
protected void logMessageDirect(final String message) {
logView.append(message);
logView.append("\n");
logScroll.fullScroll(View.FOCUS_DOWN);
}
/**
* Abstract async echo task.
*/
protected abstract class AbstractEchoTask extends Thread {
/** Handler object. */
private final Handler handler;
/**
* Constructor.
*/
public AbstractEchoTask() {
handler = new Handler();
}
/**
* On pre execute callback in calling thread.
*/
protected void onPreExecute() {
startButton.setEnabled(false);
logView.setText("");
}
public synchronized void start() {
onPreExecute();
super.start();
}
public void run() {
onBackground();
handler.post(new Runnable() {
public void run() {
onPostExecute();
}
});
}
/**
* On background callback in new thread.
*/
protected abstract void onBackground();
/**
* On post execute callback in calling thread.
*/
protected void onPostExecute() {
startButton.setEnabled(true);
}
}
static {
System.loadLibrary("Echo");
}
?? AbstractEchoActivity,除了處理houskeeping任務外例如綁定這用戶接口組件,提供一個簡單的線程實現能夠使應用程序來執行這網絡操作在單獨的線程而不是UI線程。
? Echo項目的字符串資源:
? <resources>
<string name="app_name">Echo</string>
<string name="title_activity_echo_server">Echo Server</string>
<string name="port_edit">Port Number</string>
<string name="start_server_button">Start Server</string>
<string name="title_activity_echo_client">Echo Client</string>
<string name="ip_edit">IP Address</string>
<string name="start_client_button">Start Client</string>
<string name="send_button">Send</string>
<string name="message_edit">Message</string>
<string name="title_activity_local_echo">Local Echo</string>
<string name="local_port_edit">Port Name</string>
? 原生的Echo模塊:
? 這原生的echo模塊將提供者原生socket接口方法對于這Java應用程序的實現。使用Project Explorer,展開jni目錄對于原生源文件,和雙擊這Echo.cpp C++源文件。代替它的內容如下:
? // JNI
#include <jni.h>
// NULL
#include <stdio.h>
// va_list, vsnprintf
#include <stdarg.h>
// errno
#include <errno.h>
// strerror_r, memset
#include <string.h>
// socket, bind, getsockname, listen, accept, recv, send, connect
#include <sys/types.h>
#include <sys/socket.h>
// sockaddr_un
#include <sys/un.h>
// htons, sockaddr_in
#include <netinet/in.h>
// inet_ntop
#include <arpa/inet.h>
// close, unlink
#include <unistd.h>
Download at http://www.pin5i.com/
216 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication
// offsetof
#include <stddef.h>
// Max log message length
#define MAX_LOG_MESSAGE_LENGTH 256
// Max data buffer size
#define MAX_BUFFER_SIZE 80
/**
* Logs the given message to the application.
*
* @param env JNIEnv interface.
JNIEnv* env,
jobject obj,
const char* format,
...)
{
// Cached log method ID
static jmethodID methodID = NULL;
// If method ID is not cached
if (NULL == methodID)
{
// Get class from object
jclass clazz = env->GetObjectClass(obj);
// Get the method ID for the given method
methodID = env->GetMethodID(clazz, "logMessage",
"(Ljava/lang/String;)V");
// Release the class reference
env->DeleteLocalRef(clazz);
}
// If method is found
if (NULL != methodID)
{
// Format the log message
char buffer[MAX_LOG_MESSAGE_LENGTH];
va_list ap;
va_start(ap, format);
vsnprintf(buffer, MAX_LOG_MESSAGE_LENGTH, format, ap);
va_end(ap);
Download at http://www.pin5i.com/
217 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication
// Convert the buffer to a Java string
jstring message = env->NewStringUTF(buffer);
// If string is properly constructed
if (NULL != message)
{
// Log message
env->CallVoidMethod(obj, methodID, message);
// Release the message reference
env->DeleteLocalRef(message);
}
}
}
/**
* Throws a new exception using the given exception class
* and exception message.
*
* @param env JNIEnv interface.
* @param className class name.
* @param message exception message.
*/
static void ThrowException(
JNIEnv* env,
const char* className,
const char* message)
{
// Get the exception class
jclass clazz = env->FindClass(className);
// If exception class is found
if (NULL != clazz)
{
// Throw exception
env->ThrowNew(clazz, message);
// Release local class reference
env->DeleteLocalRef(clazz);
}
}
/**
* Throws a new exception using the given exception class
* and error message based on the error number.
*
* @param env JNIEnv interface.
* @param className class name.
* @param errnum error number.
*/
static void ThrowErrnoException(
JNIEnv* env,
const char* className,
int errnum)
{
char buffer[MAX_LOG_MESSAGE_LENGTH];
// Get message for the error number
if (-1 == strerror_r(errnum, buffer, MAX_LOG_MESSAGE_LENGTH))
{
strerror_r(errno, buffer, MAX_LOG_MESSAGE_LENGTH);
}
// Throw exception
ThrowException(env, className, buffer);
?
?
通過這通信的生命周期,和處理這排序和錯誤檢查括號來自這應用程序。將修改者例子Echo應用程序來包含Tcp服務器和客戶端activites為了占建立的連接和信息交換使用sockets。
Echo Server Activity布局
?使用Project Explorer視圖,展開res目錄。展開layout子目錄,和創建一個新的布局文件叫做Activity_echo_server.xml.如下:
?? <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
Download at http://www.pin5i.com/
219 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication
<EditText
android:id="@+id/port_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/port_edit"
android:inputType="number" >
<requestFocus />
</EditText>
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/start_server_button" />
</LinearLayout>
<ScrollView
android:id="@+id/log_scroll"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/log_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
</LinearLayout>
這個Echo Server提供了一個簡單的用戶接口來獲得端口來綁定服務器和來呈現著狀態更新來著這原生Tcp 服務區在它正在執行。
Echo Server Activity:
?? 使用創建EchoServerAcitivity.java在src目錄下。
package com.apress.echo;
/**
* Echo server.
*
* @author Onur Cinar
*/
public class EchoServerActivity extends AbstractEchoActivity {
/**
* Constructor.
*/
public EchoServerActivity() {
super(R.layout.activity_echo_server);
}
protected void onStartButtonClicked() {
Integer port = getPort();
if (port != null) {
ServerTask serverTask = new ServerTask(port);
serverTask.start();
}
}
/**
* Starts the TCP server on the given port.
*
* @param port
* port number.
* @throws Exception
*/
private native void nativeStartTcpServer(int port) throws Exception;
/**
* Starts the UDP server on the given port.
*
* @param port
* port number.
* @throws Exception
*/
private native void nativeStartUdpServer(int port) throws Exception;
/**
* Server task.
*/
private class ServerTask extends AbstractEchoTask {
/** Port number. */
private final int port;
/**
* Constructor.
*
* @param port
* port number.
*/
public ServerTask(int port) {
this.port = port;
}
protected void onBackground() {
logMessage("Starting server.");
try {
nativeStartTcpServer(port);
} catch (Exception e) {
logMessage(e.getMessage());
}
logMessage("Server terminated.");
}
}
}
?? 實現原生TCP服務:
? 使用Project Explorer,選擇這EchoServerActivity,和產生C和C++頭文件。打開Echo.cpp源文件。出入include語句,如下:
? #include "com_apress_echo_EchoServerActivity.h"
??? 創建一個Socket:socket
? 一個socket通過一個叫做socket描述符的整形術來代表。Socket API函數,而不是這創建這socket本身,需要一個有效的socket描述符對于這個函數。一個socket函數被創建使用這socket函數。
?? int socket(int domain,int type,int protocol);
? 這個socket函數需要如下參數來創建一個新的socket:
? Domain指定這socketdomain,通信選擇的協議族。在這寫的期間,接下來的協議族被Android平臺所支持。
? PF_LOCAL:Host-interanl通信協議。這個通信協議能夠使用應用程序那運行在同樣的設備來使用Socket APIs來相互通信。
? PF_INET:internet 版本4端口協議。這個端口使應用協議和正在這個網絡運行的的應用程序相互通信。
? Type指定了通信的語義。接下的主要的socket類型被支持的:
?????? SOCKET_STREAM:流端口類型同基于連接的通信使用Tcp端口。
????? SOCKET_DGRAM:數據報類型提供了無連接的通信使用UDP端口。
??? Protocol指定了將要被使用的端口。對于大部分的端口協議族和類型,僅僅只用一種坑能的端口被使用。為了選擇默認的端口,這個參數被設置為0.
??? socket函數返回相關聯的socket描述符;-1和錯誤errno的全局變量被設置到這恰當的error。
?? NewTcpSocket幫助函數對于Echo.cpp原生模塊
* @throws IOException
*/
static int NewTcpSocket(JNIEnv* env, jobject obj)
{
// Construct socket
LogMessage(env, obj, "Constructing a new TCP socket...");
int tcpSocket = socket(PF_INET, SOCK_STREAM, 0);
// Check if socket is properly constructed
if (-1 == tcpSocket)
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
return tcpSocket;
}
??? 這個幫助函數創建了一個新的TCP socket和失敗是拋出一個java.lang.IOException異常。
? 綁定這Socket到一個地址:bind
? 當一個Socket被創建通過socket函數,它存在一個socket協議族空間沒有一個端口地址分配給它。對于客戶端能夠指定和連接到這個端口,它需要首先綁定一個地址。一個socket能夠綁定到一個地址使用bind函數。
?? int bind(int socketDescriptor,const struct sockaddr* address,socklen_t addressLength);
?? 這綁定函數需要如下參數為了綁定著socket到一個地址:
?? 這socket描述符指定socket實例將綁定到這給定的地址。
? 這地址指定了端地址,socket將會綁定的。
? 這地址長度指定了端口地址結構的長度被傳遞給這個函數。
? 取決于端口地址,一個不同特色的端口地址被使用。對于PF_INET端口協議,這socketaddr_in結構體被使用來指定和端口地址。這socketaddr_in結構體的定義如下:
??? struct sockaddr_in {
sa_family_t sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
}
? 如果這個端口被恰當的綁定,和bind函數將返回0;否則,它將返回-1和這errno全局變量到這恰當錯誤。
?? 使用這Editor視圖,添加這BindSocketToPort幫助函數到Echo.cpp本地模塊源文件如下:
??? /**
* Binds socket to a port number.
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param sd socket descriptor.
* @param port port number or zero for random port.
* @throws IOException
*/
static void BindSocketToPort(
JNIEnv* env,
jobject obj,
int sd,
unsigned short port)
{
struct sockaddr_in address;
// Address to bind socket
memset(&address, 0, sizeof(address));
address.sin_family = PF_INET;
// Bind to all addresses
address.sin_addr.s_addr = htonl(INADDR_ANY);
// Convert port to network byte order
address.sin_port = htons(port);
// Bind socket
LogMessage(env, obj, "Binding to port %hu.", port);
if (-1 == bind(sd, (struct sockaddr*) &address, sizeof(address)))
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
}
?? 如果端口號被設定為0在地址結構體中,這bind函數將分配第一個可能的端口號給這個socket。端口號可以被獲得通過使用getsocketname函數。如下:
??? /**
* Gets the port number socket is currently binded.
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param sd socket descriptor.
* @return port number.
* @throws IOException
*/
static unsigned short GetSocketPort(
JNIEnv* env,
jobject obj,
int sd)
{
unsigned short port = 0;
Download at http://www.pin5i.com/
225 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication
struct sockaddr_in address;
socklen_t addressLength = sizeof(address);
// Get the socket address
if (-1 == getsockname(sd,
(struct sockaddr*) &address,
&addressLength))
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
else
{
// Convert port to host byte order
port = ntohs(address.sin_port);
LogMessage(env, obj, "Binded to random port %hu.", port);
}
return port;
}
?
???? 你可能注意到,這個端口號并沒有直接的傳遞到sockaddr_in結構體。代替的是,這htons函數被用來首先做轉換。這是由于host和網絡字節的排序的不同。
網絡字節的排序:
??? 不同的機器架構使用不同的規則對于數據的排序在硬件水平。下面是著名的字節順序:
??? 大端排序:先存儲大端字節。
?? 小端排序:先存儲最后有意義的字節。
帶有不同字節排序的規則的機器不能直接交換數據。為了使用帶有不同字節排序規則的機器相互通信通過網絡,這internet端口定義了大端排序作為官方的網絡字節排序轉換對于數據的轉換。
?? 正如Java虛擬機已經使用了大端字節排序,這第一次你正聽說
字節序數據。Java應用程序并沒有做任何轉換對于數據當在網絡上傳輸數據時。相比而言,對于原生的組件不能被執行在Java虛擬機上,他們使用的機器自己排序如下:
????? ARM和x86機器架構使用小端字節排序。
????? MIPS機器架構使用達到字節排序。
當通過網絡通信,這原生的代碼必須在機器字節排序和網絡字節排序鏡像轉換。
這Socket庫提供了一套轉換函數來使原生的應用程序透明的操作字節順序的轉換。這些函數被定義通過這sys/endian.h頭文件:
???? #include<sys/endian.h>
? 這接下轉換函數被提供如下:
?? htons函數轉換一個無符號short從host機器字節順序到網絡字節順序。
?? ntohs函數和htons相反,通過轉換一個無符號的short類型數據從網絡到host機器的字節順序。
?? htonl函數轉換一個無符號的整形術從host機器字節順序到網絡字節順序。
?? ntohl函數和htonl函數相反。
? 監聽到來的連接:listen
?? 通過listen函數監聽一個socket:
? int listen(int socketDescriptor,int backlog);
?? 這個監聽函數需要如下的參數被提供來開始監聽到來的連接:
?????? 這socket描述符指定了socket實例這應用程序想要開始監聽到來的連接。
????? 這backlog指定這隊列的長度來保存著pending到來的連接。如果應用程序時繁忙的服務一個客戶端,其他到來的連接排隊到一定數量的pengding有backlog。當backlog是滿時,連接被拒絕。
如果函數成功返回0,否則-1;
/**
* Listens on given socket with the given backlog for
* pending connections. When the backlog is full, the
* new connections will be rejected.
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param sd socket descriptor.
* @param backlog backlog size.
* @throws IOException
*/
static void ListenOnSocket(
JNIEnv* env,
jobject obj,
int sd,
int backlog)
{
// Listen on socket with the given backlog
LogMessage(env, obj,
"Listening on socket with a backlog of %d pending connections.",
backlog);
if (-1 == listen(sd, backlog))
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
}
接收到來的連接:accept
? 這accept函數被明確的用來放置到來的連接從監聽隊列到接收它。
? int accept(int socketDescriptor,struct sockeaddr* address,socklen_t* addressLength);
?這個accept函數時一個阻塞函數。如果沒有阻塞到來的連接請求在監聽隊列,它放置著調用線程到一個阻塞的狀態知道一個新到來的連接到達。這accept函數需要的參數:
? ? 這socket 描述符指定了這socket的實例,應用程序想要接收一個pending的到來的連接。
? ? ?這地址的長度指針提供了一個地址結構,填充了要連接的客戶端的端口地址。如果信息不被應用程序所需要,它被設置為null。
? ? 這地址的長度指針同了連接客戶端端口地址的長度要分配的內存空間。如果信息不被需要則設置為NULL。
accept請求成功,這函數返回客戶端描述符,否則為-1和errno全局變量被設置。
? ? LogAddress幫助函數到原生模塊源文件,如下:
? ?/**
* Logs the IP address and the port number from the
* given address.
*
* @param env JNIEnv interface.
* @param obj object instance.
* @param message message text.
* @param address adress instance.
* @throws IOException
*/
static void LogAddress(
JNIEnv* env,
jobject obj,
const char* message,
const struct sockaddr_in* address)
{
char ip[INET_ADDRSTRLEN];
// Convert the IP address to string
if (NULL == inet_ntop(PF_INET,
&(address->sin_addr),
ip,
INET_ADDRSTRLEN))
Download at http://www.pin5i.com/
229 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication?
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
else
{
// Convert port to host byte order
unsigned short port = ntohs(address->sin_port);
// Log address
LogMessage(env, obj, "%s %s:%hu.", message, ip, port);
}
}
static int AcceptOnSocket(
JNIEnv* env,
jobject obj,
int sd)
{
struct sockaddr_in address;
socklen_t addressLength = sizeof(address);
// Blocks and waits for an incoming client connection
// and accepts it
LogMessage(env, obj, "Waiting for a client connection...");
int clientSocket = accept(sd,
(struct sockaddr*) &address,
&addressLength);
// If client socket is not valid
if (-1 == clientSocket)
Download at http://www.pin5i.com/
230 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication?
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
else
{
// Log address
LogAddress(env, obj, "Client connection from ", &address);
}
return clientSocket;
}
recv函數是一個阻塞函數。如果沒有數據從給定的socket被接收到,它將放著調用進程到阻塞狀態直到數據可以使用。這recv函數需要的參數如下:
? ?這socket描述符指定了socket實例,應用程序想要接收數據的socket實例。
? ? 這buffer指針是將要填充接收到數據的內存地址。
? ? Flages中指定了可惜標記。
如果recv函數成功,它將返回接收到數據的字節數目;否則,它將返回-1和errno全局變量被設置恰當的錯誤。如果函數返回0,它將指示socket是斷開連接的。使用如下:
??static ssize_t ReceiveFromSocket(
JNIEnv* env,
jobject obj,
int sd,
char* buffer,
size_t bufferSize)
{
// Block and receive data from the socket into the buffer
LogMessage(env, obj, "Receiving from the socket...");
ssize_t recvSize = recv(sd, buffer, bufferSize - 1, 0);
// If receive is failed
if (-1 == recvSize)
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
else
{
// NULL terminate the buffer to make it a string
buffer[recvSize] = NULL;
// If data is received
if (recvSize > 0)
{
LogMessage(env, obj, "Received %d bytes: %s",?
recvSize, buffer);
}
else
{
LogMessage(env, obj, "Client disconnected.");
}
}
return recvSize;
}
發送數據到Socket:send
發送數據到socket通過發送函數:
? ? ?ssize_t send(int socketDescriptor,void* buffer,size_t bufferLeng,int flags);
像recv函數,這發送函數也是個阻塞函數。如果這socket是繁忙的發送數據,它放置調用進程到一個阻塞狀態直到對于傳輸的數據準備好。這發送函數需要接下的參數被提供為了及時一個pending到來的連接:
? ? ?這socket描述符指定了應用程序想要發送數據給的socket實例
? ? ?這buffer指針指向了要發送數據的內存地址。
? ? 這buffer長度指定了這buffer的大小。這send函數僅僅傳輸者buffer到這個長度和返回。
? ? Flages指定額外的發送標記。
? ? send函數返回傳輸的的字節數據如果成功,否則-1和制定了恰當錯誤的全局變量errno。像recvSendToSocket幫助函數,如下:
? ??static ssize_t SendToSocket(
JNIEnv* env,
jobject obj,
int sd,
const char* buffer,
size_t bufferSize)
{
// Send data buffer to the socket
LogMessage(env, obj, "Sending to the socket...");
ssize_t sentSize = send(sd, buffer, bufferSize, 0);
Download at http://www.pin5i.com/
233 CHAPTER 8: POSIX Socket API: Connection-Oriented Communication?
// If send is failed
if (-1 == sentSize)
{
// Throw an exception with error number
ThrowErrnoException(env, "java/io/IOException", errno);
}
else
{
if (sentSize > 0)
{
LogMessage(env, obj, "Sent %d bytes: %s", sentSize, buffer);
}
else
{
LogMessage(env, obj, "Client disconnected.");
}
}
return sentSize;
}
? ? ?
?
?
?
總結
以上是生活随笔為你收集整理的Echo Socket例子项目的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POSIX线程的同步
- 下一篇: Socket UDP无连接通信