当前位置: 移动技术网 > IT编程>开发语言>Java > iOS socket网络编程实例详解

iOS socket网络编程实例详解

2019年07月22日  | 移动技术网IT编程  | 我要评论
代码下载 服务端代码下载地址 客户端代码下载地址 相关概念 socket是一个针对tcp和udp编程的接口,你可以借助它建立tcp连接等。socket

代码下载

服务端代码下载地址

客户端代码下载地址

相关概念

socket是一个针对tcp和udp编程的接口,你可以借助它建立tcp连接等。socket是对tcp/ip协议的封装,socket本身并不是协议,而是一个调用接口(api),通过socket,我们才能使用tcp/ip协议。socket的出现只是使得程序员更方便地使用tcp/ip协议栈而已,是对tcp/ip协议的抽象,从而形成了我们知道的一些最基本的函数接口。

socket连接:socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉;但是由于各种环境因素可能会是连接断开,比如说:服务器端或客户端主机down了,网络故障,或者两者之间长时间没有数据传输,网络防火墙可能会断开该连接以释放网络资源。所以当一个socket连接中没有数据的传输,那么为了维持连接需要发送心跳消息~~具体心跳消息格式是开发者自己定义的。

tcp连接的三次握手:

第一次握手:客户端发送syn包(syn=j)到服务器,并进入syn_send状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的syn(ack=j+1),同时自己也发送一个syn包(syn=k),即syn+ack包,此时服务器进入syn_recv状态;

第三次握手:客户端收到服务器的syn+ack包,向服务器发送确认包ack(ack=k+1),此包发送完毕,客户端和服务器进入established状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,tcp连接一旦建立,在通信双方中的任何一方主动关闭连接之前,tcp 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开tcp连接的请求,断开过程需要经过“四次握手”。

socket建立网络连接的步骤

建立socket连接至少需要一对套接字,其中一个运行于客户端,称为clientsocket ,另一个运行于服务器端,称为serversocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

1。服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

2。客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

3。连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

bsd socket

bsd socket:完全由c语言实现,并且可以在objective-c代码中使用。

优点:不同平台中易于移植

缺点:

无法访问操作系统内建的网络特性(比如系统范围的vpn)。
更糟糕的是初始化socket连接并不会自动打开设备的wi-fi或是蜂窝网络,无线网络会智能的关闭以节省电池电量,任何通信连接都会失败,除非其他网络进程激活了无线网。

cfnetwork对bsd socket的分装可以激活设备的无线网,所以几乎所有场景都建议使用cfnetwork而不是bsd socket.

最常用的api:

1.int socket(int, int, int):创建并初始化新的socket,如果成功放回一个文件描述符,如果失败返回0。

2.int bind(int, const struct sockaddr *, socklen_t):为指定的地址与端口号分配socket。

3.int listen(int, int) __darwin_alias(listen):用于服务端监听客户端

4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受连接请求,将客户端地址存储到clientaddress中。

5.int connect(int, const struct sockaddr *, socklen_t):连接到指定的服务器。

6.ssize_t send(int, const void *, size_t, int):在socket上最多发送xx数据。

7.ssize_t recv(int, void *, size_t, int):在socket上最多读取xx数据。

……

服务端逻辑:如果接收到客户端发送过来”1”,那么回复”你猜!”;客户端发送过来”2”,那么回复”谢谢!”;客户端发送过来”3”,那么回复”对不起!”;客户端发送过来”4”,那么回复”好的!”;客户端发送过来其他的内容,那么一半概率原样回复,一半概率回复”不知道你在说什么!”。

屏幕快照 2017-03-16 下午2.16.02.png

创建服务端:

1.创建socket

struct sockaddr_in server_addr;
 server_addr.sin_len = sizeof(struct sockaddr_in);
 server_addr.sin_family = af_inet;//af_inet互联网地址簇
 server_addr.sin_port = htons(port);
 server_addr.sin_addr.s_addr = inet_addr(address);
 bzero(&server_addr.sin_zero, 8);
 //创建socket
 int server_socket = socket(af_inet, sock_stream, 0);//sock_stream 有链接
 if (server_socket == -1) {
  perror("socket error!");
  return 1;
 }

2.绑定socket:将创建的socket绑定到本地的ip地址和端口,此socket是半相关的,只是负责侦听客户端的连接请求,并不能用于和客户端通信

 int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
 if (bind_result == -1) {
  perror("bind error!");

  return 1;
 }

3.listen侦听

 if (listen(server_socket, 5)) {
  perror("listen error!");
  return 1;
 }

4.接受客户端连接

int client_socket;
 socklen_t address_len;
 struct sockaddr_in client_address;
 for (; ; ) {
  address_len = sizeof(client_address);
  client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len);
  //1.使用多线程
  dispatch_async(dispatch_get_global_queue(dispatch_queue_priority_default, 0), ^{
   //这个函数进行数据操作
   str_echo(client_socket);
  });
 }

5.接收和发送数据

 char buf[1024];
 while (1) {
  bzero(buf, 1024);
  long byte_num = recv(socket, buf, 1024, 0);
  if (byte_num < 0) {
   return;
  }
  buf[byte_num] = '\0';
  printf("client said:%s\n", buf);
  char *result;
  nsstring *str = [nsstring stringwithcstring:buf encoding:nsutf8stringencoding];
  if ([str isequaltostring:@"1"]) {
   result = "你猜!";
  }
  else if ([str isequaltostring:@"2"])
  {
   result = "谢谢!";
  }
  else if ([str isequaltostring:@"3"])
  {
   result = "对不起!";
  }
  else if ([str isequaltostring:@"4"])
  {
   result = "好的!";
  }
  else
  {
   if (arc4random()%2 == 0) {
    result = "不知道你在说什么!";
   }
   else
   {
    result = buf;
   }
  }
  send(socket, result, 1024, 0);
 }

创建客户端:

struct sockaddr_in server_addr;
 server_addr.sin_len = sizeof(struct sockaddr_in);
 server_addr.sin_family = af_inet;
 server_addr.sin_port = htons(11332);
 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
 bzero(&(server_addr.sin_zero), 8);
 int server_socket = socket(af_inet, sock_stream, 0);
 if (server_socket == -1) {
  perror("socket error");
  return 1;
 }
 char receive_msg[1024];
 char reply_msg[1024];
 if (connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == 0) {
  //connect 成功之后,其实系统将你创建的socket绑定到一个系统分配的端口上,且其为全相关,包含服务器端的信息,可以用来和服务器端进行通信。
  while (1) {
   bzero(reply_msg, 1024);
   printf("replay:");
   scanf("%s", reply_msg);
   if (send(server_socket, reply_msg, 1024, 0) == -1) {
    perror("send error!");
    return 1;
   }
   bzero(receive_msg, 1024);
   long byte_num = recv(server_socket, receive_msg, 1024, 0);
   receive_msg[byte_num] = '\0';
   printf("server said:%s\n", receive_msg);
  }
 }
 return 0;

nsstream 读写文件

cocoa streams包含三个相关的类: nsstream、nsinputstream 和nsoutputstream。

nsstream:是个抽象类,定义了一些基本属性和方法;

nsinputstream:是nsstream的子类,可通过它从nsdata、file和socket中读取数据流;

nsoutputstream:也是nsstream的子类,可通过它将数据流写入nsdata、file和socket。

还可以给stream对象设置delegate(nsstreamdelegate),如果没有精确了给stream指定delegate,那么默认将delegate设置为其自己。

nsstreamdelegate只有一个方法
(void)stream:(nsstream *)astream handleevent:(nsstreamevent)eventcode

nsstreameventnone :改默认值不代表任何事件

nsstreameventopencompleted :socket被成功打开

nsstreameventhasbytesavailable :有可以读取的字节

nsstreameventhasspaceavailable :缓存中可以写入字节

nsstreameventerroroccurred :操作出现错误cfreadstreamcopyerror()和cfwritestreamcopyerror()会提供更多错误细节

nsstreameventendencountered :socket达到字节流的末尾

通过nsstream对象读数据,可以分为如下步骤完成:

a) 从数据源创建和初始化一个nsstream对象;

b) 配置run loop,并打开stream对象;

c) 响应nsinputstream事件(nsstreamdelegate);

d) 关闭nsinputstream对象。

- (void)viewdidload {
 [super viewdidload];
 nsstring *documentstr = [nssearchpathfordirectoriesindomains(nsdocumentdirectory, nsuserdomainmask, yes) firstobject];
 nsstring *datapath = [documentstr stringbyappendingpathcomponent:@"data.txt"];
 nsfilemanager *manager = [nsfilemanager defaultmanager];
 if ([manager fileexistsatpath:datapath]) {
  self.read = yes;
  nslog(@"string:%@", [nsstring stringwithcontentsoffile:datapath encoding:nsutf8stringencoding error:nil]);
  nsinputstream *instream = [[nsinputstream alloc] initwithfileatpath:datapath];
  [instream setdelegate:self];
  [instream scheduleinrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
  [instream open];
 }
 else
 {
  if ([manager createfileatpath:datapath contents:nil attributes:nil]) {
   self.read = no;
   nsoutputstream *outstream = [[nsoutputstream alloc] inittofileatpath:datapath append:yes];
   [outstream setdelegate:self];
   [outstream scheduleinrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
   [outstream open];
  }
  else
  {
   nslog(@"文件创建失败!");
  }
 }
}
#pragma mark - <nsstreamdelegate>代理方法
- (void)stream:(nsstream *)astream handleevent:(nsstreamevent)eventcode
{
 if (self.read) {
  nsinputstream *instream = (nsinputstream *)astream;
  switch (eventcode) {
   case nsstreameventhasbytesavailable:
   {
    uint8_t data[1024];
    [instream read:data maxlength:1024];
    printf("%s", data);
    [instream close];
    [instream removefromrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
    instream = nil;
   }
    break;
   case nsstreameventendencountered:
   {
    [instream close];
    [instream removefromrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
    instream = nil;
   }
    break;
   default:
    break;
  }
 }
 else{
  nsoutputstream *outstream = (nsoutputstream *)astream;
  switch (eventcode) {
   case nsstreameventhasspaceavailable:
   {
    uint8_t data[] = "{name:'张三', age:10}";
    [outstream write:data maxlength:strlen((char *)data)];
    [outstream close];
    [outstream removefromrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
    outstream = nil;
   }
    break;
   case nsstreameventendencountered:
   {
    [outstream close];
    [outstream removefromrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
    outstream = nil;
   }
    break;
   default:
    break;
  }
 }
}

nsstream socket

nsstream类不支持在ios平台上创建socket连接,而cfstream支持在ios平台的socket行为。所以若知道远程主机的dns或者是ip地址,可以使用cfstreamcreatepairwithsockettohost函数来创建socket连接,通过该函数创建了cfstream类型为全双工的socket连接,接着可以利用toll-free bridge,将cfstream对象转换为nsstream对象。

通过nsstream对象进行socket通信,与通过nsstream进行io操作的步骤基本一样:

a) 创建nsstream对象,通过cfstreamcreatepairwithsockettohost函数创建cfreadstreamref 和cfwritestreamref 对象;继而将两者转换为nsinputstream 和nsoutputstream 对象;

b) 配置run loop,并打开nsinputstream 和nsoutputstream对象;

c) 响应事件,在delegate中响应不同的信号;

d) 关闭nsstream对象。

服务端:服务端依然使用上面例子的服务端

客户端的实现:点击1~5这五个按钮,分别从服务端返回不同的数据,进而展示出来。

屏幕快照 2017-03-16 下午3.55.49.png

由于实现相对简单,就不加阐述说明了,实现代码如下:

- (void)viewdidload {
 [super viewdidload];
 [self startsocket:@"127.0.0.1" andport:11332];
}
- (void)startsocket:(nsstring *)address andport:(int)port
{
 cfreadstreamref readref;
 cfwritestreamref writeref;
 cfstreamcreatepairwithsockettohost(null, (__bridge cfstringref)address, port, &readref, &writeref);
 nsinputstream *inputstream = (__bridge nsinputstream *)readref;
 nsoutputstream *outputstream = (__bridge nsoutputstream *)writeref;
 inputstream.delegate = self;
 outputstream.delegate = self;
 [inputstream scheduleinrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
 [outputstream scheduleinrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
 [inputstream open];
 [outputstream open];
 self.outputstream = outputstream;
}
- (ibaction)sendmsg:(uibutton *)sender {
 if (sender.currenttitle > 0) {
  const char *output = sender.currenttitle.utf8string;
  [self.outputstream write:(const uint8_t *)output maxlength:strlen(output)];
 }
}
- (void)showmessage:(nsstring *)msg
{
 if (!self.msglabel) {
  uifont *font = [uifont systemfontofsize:14];
  uicolor *color = [uicolor blackcolor];
  uicolor *backcolor = [uicolor colorwithred:0 green:0 blue:0 alpha:0.2];
  uilabel *label = [[uilabel alloc] init];
  label.textcolor = color;
  label.font = font;
  label.textalignment = nstextalignmentcenter;
  label.backgroundcolor = backcolor;
  self.msglabel = label;
 }
 cgfloat screenw = [uiscreen mainscreen].bounds.size.width;
 cgfloat screenh = [uiscreen mainscreen].bounds.size.height;
 cgfloat w = screenw - 16;
 cgsize size = [msg boundingrectwithsize:cgsizemake(w, cgfloat_max) options:nsstringdrawinguseslinefragmentorigin attributes:@{nsfontattributename:self.msglabel.font} context:nil].size;
 w = size.width;
 cgfloat h = size.height;
 cgfloat x = (screenw - w)/2;
 cgfloat y = (screenh - h)/2;
 self.msglabel.frame = cgrectmake(x, y, w, h);
 self.msglabel.text = msg;
 [self.view addsubview:self.msglabel];
 dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(2.0 * nsec_per_sec)), dispatch_get_main_queue(), ^{
  [self.msglabel removefromsuperview];
 });
}
#pragma mark - <nsstreamdelegate>代理方法
- (void)stream:(nsstream *)astream handleevent:(nsstreamevent)eventcode
{
 switch (eventcode) {
  case nsstreameventnone:
   break;
  case nsstreameventopencompleted:
   break;
  case nsstreameventhasbytesavailable:
  {
   uint8_t buf[1024];
   nsinteger len = 0;
   nsinputstream *inputstream = (nsinputstream *)astream;
   len = [inputstream read:buf maxlength:1024];
   if (len) {
    [self showmessage:[nsstring stringwithcstring:(const char *)buf encoding:nsutf8stringencoding]];
   }
   break;
  }
  case nsstreameventhasspaceavailable:
   break;
  case nsstreameventerroroccurred:
  {
   [astream close];
   break;
  }
  case nsstreameventendencountered:
  {
   [astream close];
   [astream removefromrunloop:[nsrunloop currentrunloop] formode:nsdefaultrunloopmode];
   astream = nil;
   break;
  }
  default:
   break;
 }

以上所述是小编给大家介绍的ios socket网络编程,希望对大家有所帮助

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网