网络编程的一些介绍
目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提 出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也 能及时得到服务。
TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
UDP:1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
2,UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
3,UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:1,面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
2,TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
3,TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
Socket
通常我们翻译为套接字,是应用层和传输层之间的一个抽象层,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,一个Socket由一个IP地址和一个端口号唯一确定。
ServerSocket
ServerSocket用于监听来自客户端的Socket连接,如果没有连接将会一直等待下去,主要用于构建服务器端。下面是一些ServerSocket的一些常用方法
ServerSocket(int port):用指定的一个端口来创建一个ServerSocket,port的值在0-65535
ServerSocket(int port, int backlog):增加一个改变连接队列长度的参数backlog
ServerSocket(int port, int backlog, InetAddress localAddress):使用localAddress参数来将ServerSocket绑定到指定的IP
accept():如果接收到连接请求,这个方法返回一个与连接的客户端Socket对应的Socket;否则该方法处于一直等待,阻塞线程。
close():在ServerSocket使用完毕后调用该方法来关闭
Socket
客户端通常可以使用Socket的构造器来连接到指定的服务器
Socket(InetAddress/String remoteAddress, int port):创建连接到指定远程主机、远程端口的Socket,并没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默认使用系统动态分配的端口。
Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):创建指定远程主机、远程端口的Socket,并指定本地IP地址和端口,适用于本地主机有多个IP的情况
getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket里面获取数据
getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket写入数据
下面看一个简单的实例
服务端
public static void main(String[] args) throws IOException {
//创建一个ServerSocket用于监听客户端的请求
ServerSocket serverSocket = new ServerSocket(3000);
while (true) {
//当接收到客户端的请求时,产生一个对应的Socket
Socket socket = serverSocket.accept();
OutputStream os = socket.getOutputStream();
os.write("jkghjgjhg \n".getBytes("utf-8"));
os.close();
socket.close();
}
}
服务端的程序运行在PC上面,定义一个Java类直接运行就可以了。上面代码是创建了一个ServerSocket在3000的端口监听,接着是一个while的无限循环,因为一个服务端不只对应一个客户端,需要不断的接收来自所以客户端的请求,收到一个请求就产生一个Socket。ServerSocket调用accept方法来接收客户端的请求,得到一个Socket对象,向这个Socket里面写入数据,然后分别关闭流,关闭Socket。
客户端
public class MainActivity extends AppCompatActivity {
private static final String TAG = "lzy";
private Context context;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
mTextView = (TextView) findViewById(R.id.tv);
}
public void bt(View view) {
new Thread() {
@Override
public void run() {
try {
Socket socket = new Socket("172.16.7.234", 3000);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
String line = br.readLine();
Log.i(TAG, "读取数据:" + line);
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
首先是建立远程连接的Socket,然后通过这个Socket获取到数据。这里的端口号和上面的要对应,最好是使用1000以上的,因为1000以下大多是系统使用的,为了避免冲突。
设置Socket的超时时间
setSoTimeout(int timeout) :设置Socket的超时时间,不能小于0,如果使用Socket进行读写操作完成之前已经超出了这个时间,那么就会抛出SocketException。
设置Socket连接服务器超时时间
//创建一个无连接的Socket
Socket socket = new Socket();
//让Socket连接到远程服务器,如果经过1秒没有连接到则认为超时
socket.connect(new InetSocketAddress(host,port),1000);
在Socket的构造参数里面并没有设置超时时长这个参数,所以需要先创建一个无连接的Socket,调用它的connect方法连接远程服务器,而这里就可以设置一个超时时长的参数。
下面再写一个聊天室的小应用来看看Socket的使用
界面很简单,在下面发送消息,发送到服务端,然后服务端再把这条消息遍历发送给所有连接了的客户端,显示在上面。这里只是简单的实现,很多不好打地方就不要太纠结了。
服务端
在Android Studio创建一个服务端,点击File->New->New Module,创建一个Java Library,看代码
public class MyServer {
public static List listSocket = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(3000);
while (true) {
//等待连接
Socket socket = serverSocket.accept();
System.out.println("a target online");
listSocket.add(socket);
//每当客户端连接成功 后启动一条线程为该客户端服务
new Thread(new ServerThread(socket)).start();
}
}
}
这段代码首先是在3000的端口号创建一个ServerSocket,然后是一个while的死循环调用ServerSocket的accept方法不停的接收来自客户端的连接请求,然后为这个客户端开启一个线程为其服务
/**
* Created by lzy on 2017/1/23.
*/
public class ServerThread implements Runnable {
private Socket socket;
private BufferedReader br;
public ServerThread(Socket socket) throws IOException {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
}
@Override
public void run() {
String content;
//不断把客服端的数据读取出来
while ((content = readFromClient()) != null) {
//把收到的消息遍历发给每一个 连接了的客户端
for (Iterator iterator = MyServer.listSocket.iterator(); iterator.hasNext(); ) {
Socket socket = iterator.next();
//打印出客服端的Ip和端口号
System.out.println("IP:" + socket.getInetAddress() + " port:" + socket.getPort());
try {
OutputStream os = socket.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private String readFromClient() {
try {
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
MyServer.listSocket.remove(socket);
}
return null;
}
}
readFromClient用来读取接收到的客户端数据,如果发生了异常就把这个Socket移除。
客户端
MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "lzy";
private List list = new ArrayList<>();
private RecyclerView mRecyclerView;
private Context context;
private ChatAdapter adapter;
private EditText mEditText;
private ClientThread clientThread;
//用于发送接收到的服务端的消息,显示在界面上
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
list.add(msg.obj.toString());
adapter.notifyDataSetChanged();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
mRecyclerView = (RecyclerView) findViewById(R.id.rv);
mEditText = (EditText) findViewById(R.id.et);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
adapter = new ChatAdapter(context, list);
mRecyclerView.setAdapter(adapter);
clientThread = new ClientThread(handler);
new Thread(clientThread).start();
//点击发送提交数据到服务器
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
Toast.makeText(context, "提交", Toast.LENGTH_SHORT).show();
Message msg = new Message();
msg.what = 0;
msg.obj = mEditText.getText().toString().trim();
clientThread.revHandler.sendMessage(msg);
mEditText.setText("");
}
return false;
}
});
}
}
这里定义的Handler是用来接收显示更新界面的,因为当收到服务器的数据是在子线程中,更新界面操作需要在主线程中,所以需要这个Handler来处理。
当点击发送时,就把消息发送给服务器
ClientThread
/**
* Created by lzy on 2017/1/23.
*/
public class ClientThread implements Runnable {
private static final String TAG = "lzy";
private OutputStream os;
private BufferedReader br;
private Socket socket;
//用于向UI发送消息
private Handler handler;
//接收UI线程的消息(当用户点击发送)
public Handler revHandler;
public ClientThread(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
//创建一个无连接的Socket
socket = new Socket();
try {
//连接到指定的IP和端口号,并指定10s的超时时间
socket.connect(new InetSocketAddress("172.16.7.234", 3000), 10000);
//接收服务端的数据
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
//向服务端发送数据
os = socket.getOutputStream();
//读取数据会阻塞,所以创建一个线程来读取
new Thread(new Runnable() {
@Override
public void run() {
//接收服务器的消息,发送显示在主界面
String content;
try {
while ((content = br.readLine()) != null) {
Message msg = new Message();
msg.what = 1;
msg.obj = content;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
//非UI线程,自己创建
Looper.prepare();
revHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//将用户输入的内容写入到服务器,并在前面加上手机型号
try {
os.write((Build.MODEL + ":" + (msg.obj) + "\n").getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
};
Looper.loop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientThread子线程用于和服务器建立连接交互数据,当读取到服务器的数据发送给主界面显示,并且把用户发送的数据提交到服务器。都有注释就不多说了