一、前言
小生不才,学习安卓socke通信打了很多滚,遇到过很多问题,也翻过前辈们关于socket的讲解,很杂各有见解,案例也不是很完整,于是特地将此安卓Socket与pc端c#服务器的通信代码讲解与socket的资料整理一遍加深理解,希望能有所帮助,小生没写过几次博客,欢迎各位大佬提出宝贵意见。安卓客户端项目源代码与pc端C#服务器与客户端的资源已经上传:安卓Socket与pc端c#服务器的通信完整代码(包含C#服务器与安卓项目工程)-CSDN下载 https://download.csdn.net/download/zengpengqing1204/10374110
二、Socket的基本概念
1、套接字(socket)概念
Socket(套接字)用于描述IP地址和端口,是通信链的句柄,应用程序可以通过Socket向网络发出请求或者应答网络请求。
Socket是支持TCP/IP协议的网络通信的基本操作单元,是对网络通信过程中端点的抽象表示,包含了进行网络通信所必需的5种信息:连接所使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的IP地址以及远地进程的协议端口。
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
2 、建立socket连接
建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发 给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
ps:其他一些几次握手的事情我就不弄出来了,没什么太大的营养,自己了解即可,重在实战
三、Socket的使用
1、socket的构造方法
Java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的Socket客户端和服务器端。
Socket的构造方法如下:
(1)Socket(InetAddress address, int port);
(2)Socket(InetAddress address, int port, boolean stream);
(3)Socket(String host, int port);
(4)Socket(String host, int port, boolean stream);
(5)Socket(SocketImpl impl);
(6)Socket(String host, int port, InetAddress localAddr, int localPort);
(7)Socket(InetAddress address, int port, InetAddrss localAddr, int localPort);
ServerSocket的构造方法如下:
(1)ServerSocket(int port);
(2)ServerSocket(int port, int backlog);
(3)ServerSocket(int port, int backlog, InetAddress bindAddr);
其中,参数address、host和port分别是双向连接中另一方的IP地址、主机名和端口号;参数stream表示Socket是流Socket还是数据报Socket;参数localAddr和localPort表示本地主机的IP地址和端口号;SocketImpl是Socket的父类,既可以用来创建ServerSocket,也可以用来创建Socket。
2、输入流和输出流
Socket提供了方法getInputStream()和getOutPutStream()来获得对应的输入流和输出流,以便对Socket进行读写操作,这两个方法的返回值分别是InputStream和OutPutStream对象。
为了便于读写数据,我们可以在返回的输入输出流对象上建立过滤流,如PrintStream、InputStreamReader和OutputStreamWriter等。
3、关闭Socket
可以通过调用Socket的close()方法来关闭Socket。在关闭Socket之前,应该先关闭与Socket有关的所有输入输出流,然后再关闭Socket。
四、安卓与PC端的socket通讯
1.主程序代码附首页链接
package com.example.android_socket_pc_zpq3;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText ipEditText,portEditText,sendEditText;//声明输入IP编辑框,端口号编辑框,发送信息编辑框
private TextView chatTextView;//声明聊天记录显示文本
private Thread connectThread;//声明连接服务器线程
private Handler messageHandler;//声明信息处理机制函数
Socket socket;//声明socket对象
BufferedReader bufferedReader;//声明输入流对象
OutputStream outputStream;//声明输出流对象
Boolean isconnectBoolean = false;//初始化socket连接值为未连接
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();//初始化控件
}
//连接服务器线程:连接服务器并接收数据 本人觉得可以放一起写
Runnable connectRunnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
//新建socket对象,填入编辑框IP,端口号
socket=new Socket(ipEditText.getText().toString(),
Integer.valueOf(portEditText.getText().toString()));
//取得输入流、输出流
bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
outputStream=socket.getOutputStream();
//接收数据可用子线程也可直接在此线程操作
char[] buffer=new char[256];//定义数组接收输入流数据
String bufferString="";//定义一个字符接收数组数据
int conut =0;//初始化buffer数组长度为0
int tag=0;//初识写入数组的位置
//死循环重复接收输入流数据并进行处理
while (true) {
//当输入流写入buffer数组的长度大于0时 即 接收到数据时
while ((conut=bufferedReader.read(buffer))>0) {
//将buffer数组的数据全部写入bufferString字符类型
while ( tag<buffer.length) {
bufferString=bufferString+buffer[tag];
tag++;
}
//将数据给messageHandler刷新UI界面
Message msgMessage =new Message();
msgMessage.obj=bufferString;
msgMessage.what=1;
messageHandler.sendMessage(msgMessage);
//初始化数据,以便处理下一条输入流信息
tag=0;
bufferString="";
}
}
} catch (NumberFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
//初始化所有控件
private void init() {
ipEditText=(EditText) findViewById(R.id.IpeditText);
portEditText=(EditText)findViewById(R.id.PorteditText);
sendEditText=(EditText)findViewById(R.id.sendeditText);
chatTextView=(TextView)findViewById(R.id.chattextView);
//连接服务器按钮事件
findViewById(R.id.connectbutton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//逻辑判断socket是否连接上,再做操作
if (isconnectBoolean==false) {
connectThread=new Thread(connectRunnable);
connectThread.start();
Toast.makeText(MainActivity.this, "连接服务器成功", 2).show();
isconnectBoolean=true;
}
}
});
//断开服务器按钮事件
findViewById(R.id.breakbutton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//逻辑判断socket是否连接上,再做操作
if (isconnectBoolean==true) {
if (socket !=null) {
try {
//先关闭socket连接,再关闭输入流、输出流,
socket.close();
socket = null;
bufferedReader.close();
outputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//中断连接线程
connectThread.interrupt();
Toast.makeText(MainActivity.this, "断开连接!", 2).show();
isconnectBoolean=false;
}
}
});
//发送信息给服务器按钮事件
findViewById(R.id.sendbutton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
//这里可以用线程也可以不用线程
try {
//输出流写入发送编辑框的信息并指定类型UTF-8,注意要加换行
outputStream.write((sendEditText.getText().toString()+"\n").
getBytes("utf-8"));
//输出流发送至服务器
outputStream.flush();
//handlerMessage处理发送信息刷新UI界面
Message msgMessage =new Message();
msgMessage.obj=sendEditText.getText().toString();
msgMessage.what=0;
messageHandler.sendMessage(msgMessage);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
//处理接收服务器信息以及发送至服务器信息呈现聊天室效果
messageHandler = new Handler(){
public void handleMessage(android.os.Message msgMessage) {
String sendString="";
String receiveString="";
switch (msgMessage.what) {
case 0:
sendString="曾鹏晴说:"+msgMessage.obj.toString()+"\n";
chatTextView.append(sendString);
break;
case 1:
receiveString="小权权说:"+msgMessage.obj.toString()+"\n";
chatTextView.append(receiveString);
break;
default:
break;
}
}
};
}
}
2、布局文件
<RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<EditText
android:id="@+id/IpeditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="36dp"
android:ems="7"
android:text="192.168.1.101" >
<requestFocus />
</EditText>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/IpeditText"
android:layout_alignBottom="@+id/IpeditText"
android:layout_alignParentLeft="true"
android:text="IP:" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/IpeditText"
android:layout_alignBottom="@+id/IpeditText"
android:layout_toRightOf="@+id/IpeditText"
android:text="Port:" />
<EditText
android:id="@+id/PorteditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView2"
android:layout_alignBottom="@+id/textView2"
android:layout_alignParentRight="true"
android:ems="4"
android:text="8488" />
<Button
android:id="@+id/connectbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/IpeditText"
android:layout_marginTop="14dp"
android:text="连接服务器" />
<Button
android:id="@+id/breakbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/connectbutton"
android:layout_alignBottom="@+id/connectbutton"
android:layout_marginLeft="41dp"
android:layout_toRightOf="@+id/connectbutton"
android:text="断开服务器" />
<EditText
android:id="@+id/sendeditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/PorteditText"
android:layout_below="@+id/connectbutton"
android:layout_marginTop="16dp"
android:ems="10"
android:text="hello 小权权" />
<Button
android:id="@+id/sendbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/sendeditText"
android:layout_alignRight="@+id/sendeditText"
android:layout_below="@+id/sendeditText"
android:layout_marginTop="14dp"
android:text="发送信息至服务器" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/sendbutton"
android:layout_below="@+id/sendbutton"
android:layout_marginTop="14dp"
android:text="聊天记录:" />
<TextView
android:id="@+id/chattextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView3"
android:layout_toRightOf="@+id/textView1"
android:text="" />
</RelativeLayout>
3、PC端c#服务器代码附首页链接
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace socket
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
Socket ServerSocket = null;
private void btnStart_Click(object sender, RoutedEventArgs e)
{
IPEndPoint IPE = new IPEndPoint(IPAddress.Parse(tboxIP.Text), Int32.Parse(tboxPort.Text));
ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ServerSocket.Bind(IPE);
ServerSocket.Listen(10);
showmsg("服务器已启动,监听中...");
Thread thread = new Thread(ListenClientConnect);
thread.IsBackground=true;
thread.Start();
}
Dictionary<string, Socket> dic = new Dictionary<string, Socket>();
private void ListenClientConnect(object obj)
{
while (true)
{
Socket socketClient = ServerSocket.Accept() ;
string RemoteIP = socketClient.RemoteEndPoint.ToString();
dic.Add(RemoteIP, socketClient);
Dispatcher.Invoke(()=>lstboxIP.Items.Add(RemoteIP));
showmsg(RemoteIP + "已连接");
Thread recieveThread = new Thread(recievemsg);
recieveThread.IsBackground = true;
recieveThread.Start(socketClient);
}
}
private void recievemsg(object soc)
{
Socket socketClient = (Socket)soc;
while (true)
{
byte[] buffer = new byte[1024];
int n = socketClient.Receive(buffer);
//string msg = Encoding.Default.GetString(buffer, 0, n);
string msg = Encoding.UTF8.GetString(buffer, 0, n);
//可在这里指定接受数据格式
showmsg(socketClient.RemoteEndPoint.ToString()+":"+msg);
}
}
private void showmsg(string p)
{
Dispatcher.BeginInvoke(new Action(() =>
{
rtbx.AppendText(p + "\r\n");
}));
}
private void btnStop_Click(object sender, RoutedEventArgs e)
{
ServerSocket.Close();
}
private void btnSend_Click(object sender, RoutedEventArgs e)
{
showmsg(tboxMsg.Text);
string ip = lstboxIP.SelectedValue.ToString();
//byte[] by = Encoding.UTF8.GetBytes(tboxMsg.Text);
byte[] by = Encoding.Default.GetBytes(tboxMsg.Text);
dic[ip].Send(by,0);
tboxMsg.Text = "";
}
}
}
4、布局文件
<Window x:Class="socket.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Label x:Name="label" Content="IP地址:" HorizontalAlignment="Left" Margin="24,15,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="tboxIP" HorizontalAlignment="Left" Height="23" Margin="86,17,0,0" TextWrapping="Wrap" Text="192.168.1.101" VerticalAlignment="Top" Width="92"/>
<Label x:Name="label1" Content="端口:" HorizontalAlignment="Left" Margin="183,16,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="tboxPort" HorizontalAlignment="Left" Height="23" Margin="228,17,0,0" TextWrapping="Wrap" Text="8488" VerticalAlignment="Top" Width="43"/>
<Button x:Name="btnStart" Content="服务器启动" HorizontalAlignment="Left" Margin="287,19,0,0" VerticalAlignment="Top" Width="75" Click="btnStart_Click"/>
<Button x:Name="btnStop" Content="服务器停止" HorizontalAlignment="Left" Margin="377,19,0,0" VerticalAlignment="Top" Width="75" Click="btnStop_Click"/>
<ListBox x:Name="lstboxIP" HorizontalAlignment="Left" Height="180" Margin="377,65,0,0" VerticalAlignment="Top" Width="116"/>
<Button x:Name="btnSend" Content="发送信息" HorizontalAlignment="Left" Margin="377,271,0,0" VerticalAlignment="Top" Width="75" Click="btnSend_Click"/>
<TextBox x:Name="tboxMsg" HorizontalAlignment="Left" Height="23" Margin="24,270,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="320"/>
<RichTextBox x:Name="rtbx" HorizontalAlignment="Left" Height="180" Margin="24,65,0,0" VerticalAlignment="Top" Width="320">
<FlowDocument>
<Paragraph>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
</Window>
5、聊天室效果图
原文地址:https://blog.csdn.net/Zengpengqing1204/article/details/80083651