iOS开发-使用OC搭建自己的Socket 包括服务端和客服端
- 前言
- 开发须知
- 客服端
- 服务端
- 两端测试
前言
- iOS开发中需要使用到Socket通信的地方,socket分为UDP和TCP,这次分享的是基于UDP是实现的socket。
开发须知
- 七层模型
- 计算机基础
- IP地址(主机名) 本地地址127.0.0.1 主机名localhost,每台电脑都有存在一个http://www.ip138.com
- 端口号
- 和进程关联起来的(IOS App)
- 有效端口号:0~65535
- 其中0~1024由系统使用或保留
- 传输协议
- TCP ,UDP
- 关于socket
- socket编程有两种模型:SOCK_DGRAM/SOCK_STREAM
- TCP:有连接/数据可靠/无边界/双工/C/S模型
- UDP:无连接/数据不可靠/有边界/双工 /对等模型
- TCP数据的无边界(STREAM)
- 需要建立连接
- 通过三次握手完成连接(connect(ack))
- 数据没有限制
- UDP(DGRAM)
- 不需要建立链接(所以速度快)
- 只管发送,不确认对方是接收(不可靠)
- 每个数据包的大小限制在64K内
- MSG_WAITALL:标记控制数据接收必须接受到指定字节长度才返回, 利用MSG_WAITALL接收指定长度的数据,达到数据分边界.
- TCP客户端处理
- 建立socket
- 连接服务器
- 发送数据
- 循环接收数据
- 关闭连接
- UDP 客户端处理
- 建立socket
- 发送数据SendTo
- 接收数据
- 接收数据 recvFrom
- 1.5.关闭连接
客服端
- 使用GCDAsyncSocket 库辅助
- chatC.h
#import <Foundation/Foundation.h>
@interface chatC : NSObject
- (void)buildClient;
- (void)sendData:(NSString*)content;
@end
- chatC.m
#import "chatC.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
@implementation chatC
static int fd;
- (void)buildClient {
/*
ipv4
SOCK_STREAM TCP
SOCK_DGRAM UDP
SOCK_RAW 检测电脑流量
套接字 fd 是一个文件
*/
// 1.1.建立socket
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
NSLog(@"socket fail");
}
//1.2.连接服务器
struct sockaddr_in addr;
addr.sin_family = AF_INET;//AF_INET ipv4
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);//终端输入 ns -l 8888
//1 发送一个信号给服务端,在吗(ACK) 2服务端回一个(ACK)我在 3 客服端,那我们就开始吧
int result = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (result == -1) {
NSLog(@"connect fail");
}
//1.3.发送数据
[self sendData:@"123456"];
//1.4.循环接收数据
[NSThread detachNewThreadSelector:@selector(threadRecvData) toTarget:self withObject:nil];
//1.5.关闭连接
}
- (void)threadRecvData {
char buf[32];
while (1) {
/*
0 阻塞
MSG_WAITALL 等待缓存满了才不阻塞
*/
ssize_t result = recv(fd, buf, 32, MSG_WAITALL);
if (result <= 0) {
NSLog(@"recv fail!");
break;
}
buf[result] = 0;
NSLog(@"%s", buf);
}
close(fd);
}
- (void)sendData:(NSString*)content {
[NSThread detachNewThreadSelector:@selector(threadSendData:) toTarget:self withObject:content];
}
- (void)threadSendData:(NSString*)content {
if (content.length == 0) {
return;
}
//content = @"八点钟"; // content.length = 3;
const char* contC = [content UTF8String];
ssize_t result = send(fd, contC, strlen(contC), 0);
if (result < 0) {
NSLog(@"send fail");
}
}
@end
- chatcUDP.h
#import <Foundation/Foundation.h>
@interface chatcUDP : NSObject
- (void)buildClient;
- (void)sendData:(NSString*)content;
@end
- chatcUDP.m
#import "chatcUDP.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
static int fd;
static struct sockaddr_in addr;
@implementation chatcUDP
- (void)buildClient {
// 1.1.建立socket
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
NSLog(@"socket fail");
}
//1.2.连接服务器
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);//终端输入 ns -lu 8888
//1.3.发送数据
[self sendData:@"123456"];
//1.4.循环接收数据
[NSThread detachNewThreadSelector:@selector(threadRecvData) toTarget:self withObject:nil];
//1.5.关闭连接
}
- (void)threadRecvData {
char buf[32];
while (1) {
// ssize_t result = recv(fd, buf, 32, 0);
socklen_t size = (socklen_t)sizeof(addr);
ssize_t result = recvfrom(fd, buf, 32, 0, (struct sockaddr *)&addr, &size);
if (result <= 0) {
NSLog(@"recv fail!");
break;
}
buf[result] = 0;
NSLog(@"%s", buf);
}
close(fd);
}
- (void)sendData:(NSString*)content {
[NSThread detachNewThreadSelector:@selector(threadSendData:) toTarget:self withObject:content];
}
- (void)threadSendData:(NSString*)content { //聊天时udp实现的 如果用户量特别高的时候,一般都用udp来做,udp可以自己做一个接受者
if (content.length == 0) {
return;
}
//content = @"八点钟"; // content.length = 3;
const char* contC = [content UTF8String];
//因为udp是不需要连接的,所以不需要知道它是否失败
ssize_t result = sendto(fd, contC, strlen(contC), 0, (struct sockaddr *)&addr, sizeof(addr));
}
@end
- 应用
- ViewController.m
#import "ViewController.h"
#import "chatC.h"
#import "chatcUDP.h"
@interface ViewController () <UITextFieldDelegate>
{
chatC *_chatC;
chatcUDP *_chatcUdp;
}
@property(nonatomic, strong) UITextField *textField;
@end
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self.view addSubview:self.textField];
NSLog(@"==%d", getpid());
// _chatC = [chatC new];
// [_chatC buildClient];
_chatcUdp = [chatcUDP new];
[_chatcUdp buildClient];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
// [_chatC sendData:textField.text];
[_chatcUdp sendData:textField.text];
return YES;
}
- (UITextField *)textField {
if(_textField == nil) {
_textField = [[UITextField alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, 20)];
_textField.delegate = self;
}
return _textField;
}
@end
服务端
- ChatS.h
#import <Foundation/Foundation.h>
@interface ChatS : NSObject
- (int)buildServer;
@end
- ChatS.m
#import "ChatS.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/_select.h>
#include <sys/select.h>
/*
S a连接过来(fd(1))
当客户端C连接上了服务端,S端这边会给客户端创建一个套接字fd,
如果有2C,那么S端会生成两个 fd
*/
static int serverfd; // 服务端的套接字
static fd_set allfd; // fd_set (容器,看作是一个数组)存放当前所有的套字节
static fd_set changefds; // fd(1) fd(2)发消息过来, 监听得到fd的变化,把变化放到这个容器里来
static int maxfd = -1; // allfd 里面最大的fd的值
//static int maxchangefd = -1;// changefds里面最大的fd的值
// select poll
@implementation ChatS
- (int)buildServer{
// 1 .建立socket
// serverfd = 5;
serverfd = socket(AF_INET, SOCK_STREAM, 0);
if (serverfd == -1) {
NSLog(@"socket fail");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("192.168.0.108");
addr.sin_port = htons(9999);
// 2 bind 绑定端口 进程和端口进行绑定
int isreused = 0;
setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &isreused, sizeof(int));
int r = bind(serverfd, (struct sockaddr *)&addr, sizeof(addr));
if(r == -1){
printf("bind fail!\n");
return 0;
}else{
printf("bind success!\n");
}
// 3 linsten 监听 当前10代表最大监听的数量,多余的10就会拒绝
/*
一次能处理的任务数,假如一次有15个客服端连接上来了,那么他最多处理10个,其他5就会被拒绝
*/
r = listen(serverfd, 10);
if (r == -1) {
printf("listen fail\n");
return 0;
}
printf("listen success\n");
// 4 select
while (1) {
FD_ZERO(&changefds);
/*
第一次的时候,当我们创建服务端的时候,本地只有serverfd这么一个套字节
*/
FD_SET(serverfd, &changefds); //把服务端的fd添加到changefds //往容器set里面存值
maxfd = maxfd<serverfd?serverfd:maxfd;
for (int i = 0; i <= maxfd; i++) {
if (FD_ISSET(i, &allfd)) {
FD_SET(i, &changefds); // 添加客户端的fd到changefds里面
maxfd = maxfd<i?i:maxfd;
}
}
/*
select 阻塞,当客户端有相应的时候(发消息或者建立连接),阻塞就接触
有变化的fd 就保留在changefds;
轮询 套接字的状态
*/
// poll
r = select(maxfd + 1, &changefds, 0, 0, 0);
// r = select(maxfd + 1, &changefds, 0, 0, 0); // 阻塞 客户端没有响应
// 如果serverfd 还存在changefds 容器里面,那么说明serverfd有状态,这个状态代表有客户端来连接了
if (FD_ISSET(serverfd, &changefds)) { // 这个if主要是查看是否有人来连接
printf("有人来连接了\n");
int fd = accept(serverfd, 0, 0); // 接受连接(会给客服端生成一个对应的套接字fd)
if(fd == -1){
printf("连接失败\n");
break;
}
maxfd = maxfd<fd?fd:maxfd;
FD_SET(fd, &allfd);
}
// 处理客服端的事情
// 没有提供直接读取fd_set changefds里面的对象
char buf[256];
for(int i = 0; i <= maxfd; i++){ // 5 状态(客户的状态)
//FD_ISSET(i, &allfd) fd是没有断开的
if (FD_ISSET(i, &changefds) && FD_ISSET(i, &allfd)) {
r = (int)recv(i, buf, 255, 0);
if (r <= 0) {
printf("有人退出了\n");
// 把这个套字节从 allfd里面移除
FD_CLR(i, &allfd);
}
buf[r] = 0;
printf("来自客户端的数据:%d,%s\n", r, buf);
}
// 消息处理
// 广播数据 (对每一个连接上的客户端发送数据)
for (int j = 0; j <= maxfd; j++) {
if (FD_ISSET(j, &allfd)) {
r = (int)send(j, buf,strlen(buf), 0);
printf("send:%d\n", r);
}
}
}
}
// 6 close
close(serverfd);
return 0;
}
@end
两端测试
- ViewController.m
#import "ViewController.h"
#import "chatC.h"
#import "chatcUDP.h"
#import "ChatS.h"
@interface ViewController () <UITextFieldDelegate>
{
chatC *_chatC;
chatcUDP *_chatcUdp;
ChatS *chatS;
}
@property(nonatomic, strong) UITextField *textField;
@end
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self.view addSubview:self.textField];
// chatS = [ChatS new];
// [chatS buildServer];
// NSLog(@"==%d", getpid());
_chatC = [chatC new];
[_chatC buildClient];
// _chatcUdp = [chatcUDP new];
// [_chatcUdp buildClient];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
[_chatC sendData:textField.text];
// [_chatcUdp sendData:textField.text];
return YES;
}
- (UITextField *)textField {
if(_textField == nil) {
_textField = [[UITextField alloc]initWithFrame:CGRectMake(0, self.view.frame.size.height/2, self.view.frame.size.width, 20)];
_textField.delegate = self;
}
return _textField;
}
@end