Java Socket编程之TCP协议

1、概述


  网络编程,又称为Socket编程,简单来讲就是通讯的两个端点都是Socket服务,而Socket之间的数据传输本质上都是IO流。而由于网际层中不同的传输协议,主要指TCP与UDP协议,导致不同的Socket编程的方式,Java为它们提供的API实现是不同的类,但是它们都属于Socket编程的一种,所以都遵循Socket编程的基本特点。


2、TCP协议的特性。


  虽然关于TCP协议的基本特性与UDP协议的差异,想必都比较清楚,但是这里还是要简单回顾一下,因为有助于理解Java中关于TCP协议编程的一些知识点。

  主要的一点:TCP协议在数据发送前,需要通过三次握手建立连接,形成稳定的数据通道,所以TCP数据的发送与UDP不同,UDP是通过Socket服务对象来发送包数据,从而实现数据的传递,而TCP由于事先建立了连接,形成数据通道,这数据通道本质上就是IO流,而且是输入和输出的双向流。


3、Java API中UDP编程知识点。


1、Java API为TCP协议提供的类分为Socket和ServerSocket,可以简单的理解为客户端Socket和服务端Socket,更具体的讲Socket对象是主动发起连接请求的,而ServerSocket对象是等待接收连接请求的。


2、那么客户端什么时候发起连接请求呢?

  Java API中提供了两种方式,第一种是最简单和最常用的,就是通过Socket(InetAddress address,int port)Socket(String host,int port)构造函数创建Socket对象时,Socket服务将向指定的IP地址和端口号发送连接请求;第二种,是通过无参构造函数创建Socket对象,这是面向无连接的,也就是不会发起连接请求,然后再调用voidconnect(SocketAddress endpoint)或者void connect(SocketAddress endpoint,int timeout)方法返回后,将发送连接请求。那么SocketAddress类是什么类呢?通过API文档会发现,它有一个子类为InetAddressSocket,该类其实提供了InetAddress(InetAddress addr,int port)InetAddress(String host,int port)两个构造函数,可以完成对IP地址和端口的封装,而第二个void connect(SocketAddress endpoint,int timeout)方法,第二个参数是用来指定连接超时的时间,也就是说,当向服务端发出连接请求,超出指定时间服务端无响应,则抛出SocketTimeoutException异常,可以捕获这个异常,做针对处理。


3、服务端怎么接收连接请求?

  首先需要通过ServerSocket(int port)构造函数创建服务端Socket对象,参数接收一个端口号,表示该服务绑定该端口号;但此时该Socket服务并不会接收客户端的请求,而是要通过调用Socket accept()方法,该方法执行后,将侦听端口的连接请求,如果侦听不到连接请求,该方法是阻塞式方法,程序挂起;如果侦听到,它会创建一个Socket对象,并指定该Socket对象与客户端Socket通迅,并且accept()方法会返回这个Socket对象。

Java Socket编程之TCP协议_第1张图片

4、怎么实现数据的传输?

  由于客户端和服务端建立连接以后,形式了IO流通道,而Socket类提供了OutputStream getOutputStream()InputStream getInputStream()方法可以返回输入和输出流,拿到了输入和输出流,就可以充分的使用各种IO流对象实现数据的输入和输出了。

5、Socket和ServerSocket提供了close()方法关闭资源。

6、其他一些常用方法。

InetAddress getInetAddress(),返回Socket连接的地址。

void shutdownOutput(),给Socket输出流添加结束标志,可以让读取该流的read()方法结束。

setSoTimeout(int timeout),设置当通过read()方法读取该Socket流时,如果读取的时间超过指定时间,则报SocketTimeoutException异常。


3、代码示例


1、最简单的代码。


客户端:

package com.example.network;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient {

	public static void main(String[] args) throws IOException {
		
		//1、创建Socket服务,指定连接的地址和端口号,创建成功将发出连接请求
		Socket s = new Socket("127.0.0.1",4000);
		
		//2、连接成功后,将形成数据通道,即输入和输出IO流,可以通过getInpuStream()和getOutputStream()方法获取IO流对象。
		OutputStream out = s.getOutputStream();
		
		//3、获取IO对象以后,就可以通过操作IO流来对数据进行读写。
		out.write("Hello,TCPServer!".getBytes());
		
		//4.关闭流
		s.close();
	}

}

服务端:

package com.example.network;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

	public static void main(String[] args) throws IOException {
		
		//1、创建服务端Socket对象,绑定端口号。
		ServerSocket ss = new ServerSocket(4000);
		
		//2、调用accept()方法来侦听端口号是否有连接请求,有,则返回一个socket对象,该socket将会与客户端的socket形成数据通道;如果没有,则该方法一直处于阻塞状态。
		Socket s = ss.accept();
		
		//3、有了与客户端连接的socket,同样我们可以返回输入和输出流,而客户端socket服务的输出流对应是服务端socket对象的输入流。
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf,0,len));
		
		//4.关闭socket流
		s.close();
		ss.close();

	}

}


2、手机客户端访问服务端


服务端:


package com.example.network;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class AndroidTCPServer {

	public static void main(String[] args) throws IOException {
		
		ServerSocket ss = new ServerSocket(4001);
		
		Socket s = ss.accept();
		
		OutputStream out = s.getOutputStream();
		
		out.write("你好,安卓客户端!".getBytes("utf-8"));
	
		
		s.close();
		ss.close();
	}

}


客户端:

package com.noodles.networktest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;


public class AndroidTCPClientActivity extends Activity {

    public static final int UPDATE_TEXT = 1;
    private TextView textView;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case  UPDATE_TEXT:
                    String text = (String) msg.obj;
                    textView.setText(text);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tcp_client_activity);
        textView = (TextView) findViewById(R.id.text_view);

        connectToTCPServer();

    }

    private void connectToTCPServer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Socket s = new Socket("192.168.1.101",4001);
                    InputStream in = s.getInputStream();
                    byte[] buf = new byte[1024];
                    int len = in.read(buf);
                    String text = new String(buf,0,len);
                    Message msg = new Message();
                    msg.what = UPDATE_TEXT;
                    msg.obj = text;
                    handler.sendMessage(msg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

结果:

Java Socket编程之TCP协议_第2张图片



注意点:

1、在服务端中,通过IO输出流写入中文时,没有使用包装流,并且通过String方法getBytes()指定了转换字符集utf-8,因为本人是Windows操作系统,当java程序中涉及到字符转换的时候,默认是使用的平台的字符集,而Windows默认字符集是GBK,但是Android系统是linux核心,使用的是utf-8,所以如果不指定相同的字符集,就会出现乱码。而现在字符集一般都是使用utf-8或者utf-16。

2、Android使用了多线程机制,因为Android不能在主线程进行网络访问。

3、因为需要对主线程的UI进行更新,所以使用Android平台提供的异步信息处理机制之一Handler。


3、多线程


  先思考一个问题,服务端往往是需要处理多个客户端请求的,比如服务端提供了一个2G的游戏程序资源让去用户下载,在下载资源的过程中其实服务端在向输出流中写入数据,但是这个过程可能是漫长,可能需要半个小时,那么在下载过程中,别的用户是没办法下载的,因为服务端程序在执行IO流的操作,没办法执行accept()方法,就不能接收其它客户端的请求,所以我们需要把耗时IO流操作放入子线程中,这样主线程用来接收请求,而一旦有客户端请求,就开辟一个线程去做耗时IO流处理。


简答的手机聊天程序


服务端:

package com.example.network;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class ChatServer {
	
	static List list = new ArrayList();

	public static void main(String[] args) throws IOException {
		

		
		ServerSocket ss = new ServerSocket(4002);
		
		while(true){
			Socket s = ss.accept();
			
			String ip = s.getInetAddress().getHostAddress();
			System.out.println(ip+" is connected......");
			
			list.add(s);		
			new Thread(new Chat(s)).start();
		}
	}

}

服务端流操作:

package com.example.network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;

public class Chat implements Runnable{
	private Socket s;
	Chat(Socket s){
		this.s = s;
	}
	@Override
	public void run() {
		try {
			InputStream in = s.getInputStream();

			byte[] buf = new byte[1024];
			int len;
			while((len = in.read(buf))!= -1){
				String str = new String(buf,0,len,"utf-8");
				for(Iterator iterator =ChatServer.list.iterator();iterator.hasNext();){
					try{
						Socket s = iterator.next();
						String ip = s.getInetAddress().getHostAddress();
						System.out.println("size:"+ChatServer.list.size() +"," +ip+":"+str);
						OutputStream out = s.getOutputStream();
						out.write((ip+":"+str).getBytes("utf-8"));
					}catch(IOException e){
						iterator.remove();
						System.out.println(ChatServer.list);
					}
				}
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

手机端:

package com.noodles.networktest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.IOException;


public class ChatActivity extends Activity {

    static final int SEND_TEXT = 1;
    static final int UPDATE_TEXT = 2;
    private TextView textView;
    private EditText inputText;
    private Button send;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    textView.append(System.getProperty("line.separator")+(String)(msg.obj));
                    break;
                default:
                    break;
            }
        }
    };
    private ChatClient chatClient;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat_activity);

        textView = (TextView) findViewById(R.id.text_view);
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);

        chatClient = new ChatClient(handler);
        new Thread(chatClient).start();

        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String text = inputText.getText().toString();
                if (!TextUtils.isEmpty(text)) {
                    Message message = new Message();
                    message.what = SEND_TEXT;
                    message.obj = text;
                    chatClient.mHandler.sendMessage(message);
                    inputText.setText("");
                }
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            chatClient.s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package com.noodles.networktest;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;


class ChatClient implements Runnable {

    Handler mHandler;
    private Handler uiHandler;
    private InputStream in;
    private OutputStream out;
    Socket s;

    ChatClient(Handler uiHandler) {
        this.uiHandler = uiHandler;
    }

    @Override
    public void run() {
        try {
            s = new Socket();
            s.connect(new InetSocketAddress("192.168.1.101", 4002), 5000);
            in = s.getInputStream();
            out = s.getOutputStream();
            new Thread() {
                @Override
                public void run() {
                    byte[] buf = new byte[1024];
                    int len;
                    try {
                        while ((len = in.read(buf)) != -1) {
                            String text = new String(buf, 0, len, "utf-8");
                            Log.d("ChatActivity", text);
                            Message msg = new Message();
                            msg.what = ChatActivity.UPDATE_TEXT;
                            msg.obj = text;
                            uiHandler.sendMessage(msg);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();

            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case ChatActivity.SEND_TEXT:
                            String text = (String)(msg.obj);
                            try {
                                out.write(text.getBytes());
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            break;
                        default:
                            break;
                    }
                }
            };
            Looper.loop();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果:

Java Socket编程之TCP协议_第3张图片

你可能感兴趣的:(网络编程)