同步套接字通信
socket支持下的网上点对点的通信
服务端实现监听连接,客户端实现发送连接请求,建立连接后进行发送和接收数据的功能
服务器端建立一个socket,设置好本机的ip和监听的端口与socket进行绑定,开始监听连接请求,当接收到连接请求后,发送确认,同客户端建立连接,开始与客户端进行通信。
客户端建立一个socket,设置好服务器端的ip和提供服务的端口,发出连接请求,接收到服务的确认后,尽力连接,开始与服务器进行通信。
服务器端和客户端的连接及它们之间的数据传送均采用同步方式。
socket
socket是tcp/ip网络协议接口。内部定义了许多的函数和例程。可以看成是网络通信的一个端点。在网络通信中需要两个主机或两个进程。通过网络传递数据,程序在网络对话的每一端需要一个socket。
tcp/ip传输层使用协议端口将数据传送给一个主机的特定应用程序,协议端口是一个应用程序的进程地址。传输层模块的网络软件模块要于另一个程序通信,它将使用协议端口,socket是运行在传输层的api,使用socket建立连接发送数据要指定一个端口给它。
socket:
stream socket流套接字 socket提供双向、有序、无重复的数据流服务,出溜大量的网络数据。
dgram socket数据包套接字 支持双向数据流,不保证传输的可靠性、有序、无重复。
row socket 原始套接字 访问底层协议
建立socket 用c#
命名空间:using system.net;using system.net.socket;
构造新的socket对象:socket原型:
public socket (addressfamily addressfamily,sockettype sockettype,protocoltype protocoltype)
addressfamily 用来指定socket解析地址的寻址方案。internetwork标示需要ip版本4的地址,internetworkv6需要ip版本6的地址
sockettype参数指定socket类型raw支持基础传输协议访问,stream支持可靠,双向,基于连接的数据流。
protocoltype表示socket支持的网络协议
定义主机对象:
ipendpoint类:ipendpoint构造方法 位置:system.net
原型:1) public ipendpoint(ipaddress address,int port) 2)public ipendpoint(long address,int port) 参数1整型int64如123456,参数2端口int32
主机解析:
利用dns服务器解析主机,使用dns.resolve方法
原型:public static iphostentry resolve(string hostname) 参数:待解析的主机名称,返回iphostentry类值,iphostentry为internet主机地址信息提供容器,该容器提供存有ip地址列表,主机名称等。
dns.gethostbyname获取本地主机名称
原型:public static iphostentry gethostbyname(string hostname)
gethostbyaddress
原型:1)public static iphostentry gethostbyaddress(ipaddress address) 参数:ip地址 2)public static iphostentry gethostbyaddress(string address) ip地址格式化字符串
端口绑定和监听:
同步套接字服务器主机的绑定和端口监听
socket类的bind(绑定主机),listen(监听端口),accept(接收客户端的连接请求)
bind:原型:public void bind(endpoint localep)参数为主机对象 ipendpoint
listen:原型:public void listen(int backlog) 参数整型数值,挂起队列最大值
accept:原型:public socket accept() 返回为套接字对象
演示程序:
ipaddress myip=ipaddress.parse(“127.0.0.1”);
ipendpoint myserver=new ipendpoint(myip,2020);
socket sock=new socket(addressfamily.internetwork,sockettype.stream,protocoltype.tcp);
sock.bind(myserver);
sock.listen(50);
socket bbb=sock.accept();
发送数据:方法1:socket类的send方法二:networkstream类write
send原型:public int send(byte[] buffer) 字节数组
public int send(byte[],socketflags)原型2说明,socketflags成员列表:dontroute(不使用路由表发送),maxiovectorlength(为发送和接收数据的wsabuf结构数量提供标准值)none 不对次调用使用标志) outofband(消息的部分发送或接收)partial(消息的部分发送或接收) peek(查看传入的消息)
原型三:public int send(byte[],int,socketflags) 参数二要发送的字节数
原型四:public int send(byte[],int,int,socketflags) 参数二为byte[]中开始发送的位置
演示:
socket bbb=sock.accept();
byte[] bytes=new byte[64];
string send="aaaaaaaaaaaa";
bytes=system.text.encoding.bigendianunicode.getbytes(send.tochararray());
bbb.send(bytes,bytes.length,0);//将byte数组全部发送
networdstream类的write方法发送数据
原型:public override void write(byte[] buffer,int offset,int size) 字节数组,开始字节位置,总字节数
socket bbb=sock.accept();
networkstream stre=new newworkstream(bbb);
byte[] ccc=new byte[512];
string sendmessage="aaaaaaaaaaaaaa";
ccc=system.text.encoding.bigendianunicode.getbytes(sendmessage);
stre.write(ccc,0,ccc.length);
接收数据:socket类receive或networkstream类read
socket类receive方法
原型:public int receive(byte[] buffer)
2)public int receive(byte[],socketflags)
3)public int receive(byte[],int,socketflags)
4)public int receive(byte[],int,int,socketflags)
.....
socket bbb=sock.accept();
........
byte[] ccc=new byte[512];
bbb.receive(ccc,ccc.length,0);
string rece=system.text.encoding.bigendianunicode.getstring(ccc);
richtextbox1.appendtext(rece+"/r/n");
networkstream类的read方法接收数据
public override int read(int byte[] buffer,int offset,int size)
演示:bbb=sock.accept();
.......
networkstream stre=new networkstream(bbb);
byte[] ccc=new byte[512];
stre.read(ccc,0,ccc.length);
string readmessage=system.text.encoding.bigendianunicode.getstring(ccc);
线程
线程创建:system.threading空间下的thread类的构造方法:
原型:public thread(threadstart start) threadstart类型值
thread thread=new thread(new threadstart(accp));
private void accp(){}//使用线程操作
线程启动
thread thread=new thread(new threadstart(accp));
线程暂停与重新启动
启动线程使用thread.sleep是当前线程阻塞一段时间thread.sleep(timeout.infinite)是线程休眠,直到被调用thread.interrrupt的另一个线程中断或被thread.abort中止。
一个线程不能对另一个调用sleep,可以使用thread.suspend来暂停线程,当线程对自身调用thread.suspend将阻塞,直到该线程被另一个线程继续,当一个线程对另一个调用,该调用就成为使另一个线程暂停的非阻塞调用。调用thread.resume使另一个线程跳出挂起状态并使该线程继续执行,而与调用thread.suspend的次数无关
线程休眠:thread.sleep(10000);
线程挂起:thread thread=new thread(new threadstart(accp));
thread.start();
thread.suspend();
重新启动:thread thread=new thread(new threadstart(accp));
thread.start();
thread.suspend();
thread.resume();
阻塞线程的方法:thread.join使用一个线程等待另一个线程停止
thread.join
public void join();
public void join(int millisecondstimeout);毫秒
public bool join(timespan timeout);时间间隔类型值
实例:thread thread=new thread(new threadstart(accp));
thread.start();
thread.join(10000);
线程销毁:
thread.abort,thread.interrupt
abort方法引发threadabortexception,开始中止此线程的过程,是一个可以由应用程序代码捕获的特殊异常,resetabort可以取消abort请求,可以组织threadabortexception终止此线程,线程不一定会立即终止,根本不终止。
对尚未启动的线程调用abort,则当调用start时该线程将终止。对已经挂起的线程调用abort,则该线程将继续,然后终止。对阻塞或正在休眠的线程调用abort,则该线程被中断,然后终止。
thread类的abort方法:
public void abort()
public void abort(object stateinfo);
演示:
thread thread=new thread(new threadstart(accp));
thread.start();
thread.abort();
thread.join(10000);
socket编程原理:
unix的i/o命令集,模式为开-读/写-关 open write/read close
用户进程进行i/o操作
用户进程调用打开命令,获取文件或设备的使用权,并返回描述文件或设备的整数,以描述用户打开的进程,该进程进行读写操作,传输数据,操作完成,进程关闭,通知os对哪个对象进行了使用。
unix网络应用编程:bsd的套接字socket,unix的system v 的tli。
套接字编程的基本概念:
网间进程通信:源于单机系统,每个进程在自己的地址范围内进行运行,保证互相不干扰且协调工作。操作系统为进程之间的通信提供设施:
unix bsd 管道pipe,命名管道named pipe软中断信号signal
unix system v 消息message 共享存储区 shared memory 信号量semaphore
以上仅限于本机进程之间通信。
端口:网络上可以被命名和寻址的通信端口,是操作系统可以分配的一种资源,网络通信的最终地址不是主机地址,是可以描述进程的摸中标识符。tcp/ip提出协议端口porotocol port端口,表示通信进程。
进程通过os调用绑定连接端口,而在传输层传输给该端口的数据传入进程中处理,同样在进程的数据需要传给传输层也是通过绑定端口实现。进程对端口的操作相当于对os中的i/o文件进行操作,每一个端口也对应着一个端口号,tcp/ip协议分为tcp和udp,虽然有相同port number的端口,但是互相也不冲突。 端口号的分配有全局分配,本地分配(动态分配),当进程需要访问传输层,os分配给进程一个端口号。全局分配,就是os固定分配的端口,标准的服务器都有固定的全局公认的端口号提供给服务。小于256的可以作为保留端口。
地址:网络通信中的两台机器,可以不再同一个网络,可能间隔(网关,网桥,路由器等),所以可以分为三层寻址
机器在不同的网络则有该网络的特定id
同一个网络中的机器应该有唯一的机器id
一台机器内的进程应该有自己的唯一id
通常主机地址=网络id+主机id tcp/ip中使用16位端口号来表示进程。
网络字节顺序,高价先存,tcp和udp都使用16或32整数位的高价存储,在协议的头文件中。
半相关:在网络中一个进程为协议+本地地址+端口号=三元组,也叫半相关,表示半部分。
全相关:两台机器之间通信需要使用相同协议
协议+本地地址+本地端口号+远程地址+远程端口号 五元组 全相关。
顺序:两个连续的报文在网络中可能不会通过相同的路径到达,所以接收的顺序会和发送的顺序不一致。顺序是接收顺序与发送顺序一致。tcp/ip提供该功能。
差错控制:检查数据差错:检查和checksum机制 检查连接差错:双方确认应答机制。
流控制:双方传输数据过程中,保证数据传输速率的机制,保证数据不丢失。
字节流:把传输中的报文当作一个字节序列,不提供任何数据边界。
全双工/半双工:两个方向发送或一个方向发送
缓存/带外数据:字节流服务中,没有报文边界,可以同一时刻读取任意长度的数据。为保证传输正确或流协议控制,需要使用缓存,交互型的应用程序禁用缓存。
数据传送中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息(unix系统的中断键delete,control-c)、终端流控制符control-s、control-q)为带外数据。
客户/服务器模式主动请求方式:
1. 打开通信通道,通知本地主机,在某一个公认地址上接收客户请求
2. 等待客户请求到达端口
3. 接收到重复服务请求,处理请求发送应答信号。接收到并发服务请求。要激活一个新进程处理客户请求,unix系统fork、exec,新进程处理客户请求,不需要对其他请求作出应答,服务完成后,关闭此进程与客户的通信链路。终止
4. 返回第二步,等待另一个客户请求。
5. 关闭服务端
客户方:
1. 打开一通信通道,并连接到服务器所在主机的特定端口。
2. 向服务器发送服务请求报文,等待并接收应答;继续提出请求…….
3. 请求结束以后关闭通信通道并终止。
:
1. 客户与服务器进程的作用非对称,编码不同
2. 服务进程先于客户请求而启动,系统运行,服务进程一致存在,直到正常退出或强迫退出
套接字类型:
tcp/ip的socket
sock_stream可靠的面对连接数据传输,无差错、无重复发送,安照顺序发送接收,内设流量控制,避免数据流超限,数据为字节流,无长度限制,ftp流套接字。
sock_dgram 无连接的服务,数据包以独立包的形式发送,不提供无措保证,数据可能丢失重复,发送接收的顺序混乱,网络文件系统nfs使用数据报式套接字。
sock_ram 接口允许较底层协议,ip,icmp直接访问,检查新的协议实现或访问现有服务中配置的新设备。
服务端:
using system.net;
using system.net.sockets;
using system.text;
using system.threading;
thread mythread ;
socket socket;
// 清理所有正在使用的资源。
protected override void dispose( bool disposing )
{
try
{
socket.close();//释放资源
mythread.abort ( ) ;//中止线程
}
catch{ }
if( disposing )
{
if (components != null)
{
components.dispose();
}
}
base.dispose( disposing );
}
public static ipaddress getserverip()
{
iphostentry ieh=dns.gethostbyname(dns.gethostname());
return ieh.addresslist[0];
}
private void beginlisten()
{
ipaddress serverip=getserverip();
ipendpoint iep=new ipendpoint(serverip,8000);
socket=new
socket(addressfamily.internetwork,sockettype.stream,protocoltype.tcp);
byte[] bytemessage=new byte[100];
this.label1.text=iep.tostring();
socket.bind(iep);
// do
while(true)
{
try
{
socket.listen(5);
socket newsocket=socket.accept();
newsocket.receive(bytemessage);
string stime = datetime.now.toshorttimestring ( ) ;
string msg=stime+":"+"message from:";
msg+=newsocket.remoteendpoint.tostring()+encoding.default.getstring(bytemessage);
this.listbox1.items.add(msg);
}
catch(socketexception ex)
{
this.label1.text+=ex.tostring();
}
}
// while(bytemessage!=null);
}
//开始监听
private void button1_click(object sender, system.eventargs e)
{
try
{
mythread = new thread(new threadstart(beginlisten));
mythread.start();
}
catch(system.exception er)
{
messagebox.show(er.message,"完成",messageboxbuttons.ok,messageboxicon.stop);
}
}
客户端:
using system.net;
using system.net.sockets;
using system.text;
private void button1_click(object sender, system.eventargs e)
{
beginsend();
}
private void beginsend()
{
string ip=this.txtip.text;
string port=this.txtport.text;
ipaddress serverip=ipaddress.parse(ip);
int serverport=convert.toint32(port);
ipendpoint iep=new ipendpoint(serverip,serverport);
byte[] bytemessage;
// do
// {
socket socket=new socket(addressfamily.internetwork,sockettype.stream,protocoltype.tcp);
socket.connect(iep);
bytemessage=encoding.ascii.getbytes(textbox1.text);
socket.send(bytemessage);
socket.shutdown(socketshutdown.both);
socket.close();
// }
// while(bytemessage!=null);
}
基于tcp协议的发送和接收端
tcp协议的接收端
using system.net.sockets ; //使用到tcplisten类
using system.threading ; //使用到线程
using system.io ; //使用到streamreader类
int port = 8000; //定义侦听端口号
private thread ththreadread; //创建线程,用以侦听端口号,接收信息
private tcplistener tltcplisten; //侦听端口号
private bool blistener = true; //设定标示位,判断侦听状态
private networkstream nsstream; //创建接收的基本数据流
private streamreader srread;
private system.windows.forms.statusbar statusbar1;
private system.windows.forms.button button1;
private system.windows.forms.listbox listbox1; //从网络基础数据流中读取数据
private tcpclient tcclient ;
private void listen ( )
{
try
{
tltcplisten = new tcplistener ( port ) ; //以8000端口号来初始化tcplistener实例
tltcplisten.start ( ) ; //开始监听
statusbar1.text = "正在监听..." ;
tcclient = tltcplisten.accepttcpclient ( ) ; //通过tcp连接请求
nsstream = tcclient.getstream ( ) ; //获取用以发送、接收数据的网络基础数据流
srread=new streamreader(nsstream);//以得到的网络基础数据流来初始化streamreader实例
statusbar1.text = "已经连接!";
while( blistener ) //循环侦听
{
string smessage = srread.readline();//从网络基础数据流中读取一行数据
if ( smessage == "stop" ) //判断是否为断开tcp连接控制码
{
tltcplisten.stop(); //关闭侦听
nsstream.close(); //释放资源
srread.close();
statusbar1.text = "连接已经关闭!" ;
ththreadread.abort(); //中止线程
return;
}
string stime = datetime.now.toshorttimestring ( ) ; //获取接收数据时的时间
listbox1.items.add ( stime + " " + smessage ) ;
}
}
catch ( system.security.securityexception )
{
messagebox.show ( "侦听失败!" , "错误" ) ;
}
}
//开始监听
private void button1_click(object sender, system.eventargs e)
{
ththreadread = new thread ( new threadstart ( listen ) );
ththreadread.start();//启动线程
button1.enabled=false;
}
// 清理所有正在使用的资源。
protected override void dispose( bool disposing )
{
try
{
tltcplisten.stop(); //关闭侦听
nsstream.close();
srread.close();//释放资源
ththreadread.abort();//中止线程
}
catch{}
if( disposing )
{
if (components != null)
{
components.dispose();
}
}
base.dispose( disposing );
}
tcp协议的发送端
using system.net.sockets; //使用到tcplisten类
using system.threading; //使用到线程
using system.io; //使用到streamwriter类
using system.net; //使用ipaddress类、iphostentry类等
private streamwriter swwriter; //用以向网络基础数据流传送数据
private networkstream nsstream; //创建发送数据的网络基础数据流
private tcpclient tcpclient;
private system.windows.forms.button button1;
private system.windows.forms.textbox textbox1;
private system.windows.forms.button button2;
private system.windows.forms.textbox textbox2;
private system.windows.forms.statusbar statusbar1;
private system.windows.forms.label label1;
private system.windows.forms.label label2; //通过它实现向远程主机提出tcp连接申请
private bool tcpconnect = false; //定义标识符,用以表示tcp连接是否建立
//连接
private void button1_click(object sender, system.eventargs e)
{
ipaddress ipremote ;
try
{
ipremote = ipaddress.parse ( textbox1.text ) ;
}
catch //判断给定的ip地址的合法性
{
messagebox.show ( "输入的ip地址不合法!" , "错误提示!" ) ;
return ;
}
iphostentry iphost ;
try
{
iphost = dns.resolve ( textbox1.text ) ;
}
catch //判断ip地址对应主机是否在线
{
messagebox.show ("远程主机不在线!" , "错误提示!" ) ;
return ;
}
string shostname = iphost.hostname ;
try
{
tcpclient tcpclient = new tcpclient(shostname,8000);//对远程主机的8000端口提出tcp连接申请
nsstream = tcpclient.getstream();//通过申请,并获取传送数据的网络基础数据流
swwriter = new streamwriter(nsstream);//使用获取的网络基础数据流来初始化streamwriter实例
button1.enabled = false ;
button2.enabled = true ;
tcpconnect = true ;
statusbar1.text = "已经连接!" ;
}
catch
{
messagebox.show ( "无法和远程主机8000端口建立连接!" , "错误提示!" ) ;
return ;
}
}
//发送
private void button2_click(object sender, system.eventargs e)
{
if (textbox2.text !="")
{
swwriter.writeline(textbox2.text);//刷新当前数据流中的数据
swwriter.flush();
}
else
{
messagebox.show("发送信息不能为空!","错误提示!");
}
}
// 清理所有正在使用的资源。
protected override void dispose( bool disposing )
{
if ( tcpconnect )
{
swwriter.writeline ( "stop" ) ; //发送控制码
swwriter.flush (); //刷新当前数据流中的数据
nsstream.close (); //清除资源
swwriter.close ();
}
if( disposing )
{
if (components != null)
{
components.dispose();
}
}
base.dispose( disposing );
}
异步套接字
beginaccept
public iasyncresult beginaccept{asynccallback callback,object state}
asynccallback异步回调方法 object state自定义对象, 返回iasyncresult
using system;
namespace mysocket
{
public class stateobject
{
public stateobject(){构造函数逻辑}
}
}à>
using system;
using system.net;
using system.net.sockets;
using system.threading;
using system.text;
namespace mysocket
{
public class stateobject
{
public socket worksocket=null;
public const int buffersize=1024;
public byte[] buffer=new byte[buffersize];
public stringbuilder sb=new stringbuilder();
public stateobject()
{}
}
}
实现主机绑定和端口监听:
private ipaddress myip=ipaddress.parse(“127.0.0.1”);
private ipendpoint myserver;
private socket mysocket;
private socket handler;
private static manualresetevent myreset =new manualresetevent(false);
try
{
iphostentry myhost=new iphostentry();
myhost=dns.gethostbyname(“”);
string ipstring =myhost.addresslist[0].tostring();
myip=ipaddress.parse(ipstring);
}
catch{messagebox.show(“您输入的ip地址格式不正确,重新输入!”);}
try
{
myserver=new ipendpoint(myip,int32.parse(“port”));
mysocket=new socket(addressfamily.internetwork,sockettype.stream,protocol.tcp);
mysocket.bind(myserver);
mysocket.listen(50);
thread thread=new thread(new threadstart(target));
thread.start();
}
catch(exception ee){}
线程target
private void target()
{
while(true)
{
myreset.reset();
mysocket.beginaccept(new asynccallback(acceptcallback),mysocket);
myreset.waitone();
}
}
异步回调方法acceptcallback
private void acceptcallback(iasyncresault ar)
{
myreset.set();
socket listener=(socket)ar.asyncstate;
handler=listener.endaccept(ar);
stateobject state=new stateobject();
state.worksocket=handler;
try
{
byte[] bytedata=system.text.encoding.bigendianunicode.getbytes(“通话!”+”/n/r”);
handler.beginsend(byutedata,0,bytedata.length,0,new asynccallback(sendcallback),handler);
}
catch(exception ee)
{messagebox.show(ee.message);}
thread thread=new thread(new threadstart(begreceive));
thread.start();
}
多线程:
每个窗体自己都在不同的线程上面运行,如果需要在窗体之间交互,需要在线程之间交互
当线程sleep,系统就使之退出执行队列,当睡眠结束,系统产生时钟中断,使该线程回到执行队列中,回复线程的执行。
如果父线程先于子线程结束,那么子线程在父线程结束的时候被迫结束,thread.join()是父线程等待子线程结束。abort带来的是不可回复的终止线程
起始线程为主线程,前台线程全部结束,则主线程可以终止,后台线程无条件终止。
前台线程不妨碍程序终止,一旦进程的所有前台线程终止,则clr调用任意一个还存活的后台线程的abort来彻底终止进程。
挂起,睡眠(阻塞,暂停)
thread.suspend不会使线程立即停止执行,直到线程到达安全点的时候它才可以将该线程挂起,如果线程尚未运行或这已经停止,则不能被挂起,调用thread.resume使另一个线程跳出挂起状态,继续执行。
一个线程不能对另一个线程调用sleep,但是可以suspend。
lock可以把一段代码定义为互斥段critical section 互斥段在一个时刻内只允许一个线程进入执行,其他线程必须等待
多线程公用对象,不应该使用lock,monitor提供了线程共享资源的方案,monitor可以锁定一个对象,线程只有得到锁才可以对该对象进行操作
一个进程开始至少有一个主线程。系统加载程序时创建主执行线程
消息队列与线程相关
一开始创建线程就产生消息队列了,一个线程可以创建多个窗体,而发给这些窗体的消息都同意发送到同一个消息队列中了,消息结构中有msg.hwnd指出该条消息与哪个窗体相关
dispatchmessage()函数依照这个保证消息分派处理自动化而且不会出错。
线程控制方法:
start线程开始运行
sleep 是线程暂停一段指定时间
suspend 线程在到达安全点后暂停
abort 线程到达安全点后停止
resume 重新启动挂起的线程
join 当前线程等待其他线程运行结束。如果使用超时值,且线程在分配的时间内结束,方法返回true
安全点:代码中的某些位置,这些位置公共语言运行时可以安全的执行自动垃圾回收,即释放未使用的变量并回收内存,调用线程的abort和suspend方法时,公共语言运行时将分析代码并确定线程停止运行的适当位置。
新闻热点
疑难解答
图片精选