7层: 应用层, 表示层,会话层, 传输层, 网络层(协议), 数据链路层(联网),物理层(硬件)
socket是传输层 : http是处于应用层
用OC里的C写的 双向 即时
//网络字节序指的是大端模式
//大端高位在低地址,低位在高地址
//小端高位在高地址,低位在低地址
//字节序
//导入需要的系统头文件
#import
#import
#import
#import
#import
//配置文件
#import"LPSocket.h"
//*
1.创建socket对象
1.1创建CFSocketContext对象
1.2 实现socket对象初始化中的TCPServerConnectCallBack的回调方法
1.2.1判断回调是否成功
1.2.2创建self对象 开辟分线程,实现线程方法
1.2.3self对象调用登录方法
1.3判断socket对象是否创建成功
1.4配置socket的地址信息
1.4.1设置地址
1.4.2设置消息循环
1.4.3将消息源添加到消息循环中
1.4.3对于创建的对象进行释放
2.解析终端登录包
2.1计算字节长度
2.2除去长度的字节长度
2.3设置标志,命令号
2.4发送请求
3.解析心跳包
3.1解析读取心跳包的数据
3.2再解析 发送请求
- (void)viewDidLoad {
[super viewDidLoad];
[self connectToServer];
}
//打开socket连接,长连接双向的
- (void) connectToServer {
//self一个任意指针的数据,可以用在创建CFSocket对象时相关联。这个指针被传递给所有的上下文中定义的回调。
//创建一个结构体
//在MRC环境下的开发
CFSocketContext CTX = {0,self,NULL,NULL,NULL};
//kCFAllocatorDefault一都使用默认的
//PF_INET使用ipv4 : PF_INET6使用ipv6
//TCPServerConnectCallBack回调的方法名需要实现回调方法点两下
//kCFSocketConnectCallBack指定当连接到服务器的时候回调
// &CTX提前设置的结构体
_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack, TCPServerConnectCallBack, &CTX);
//判断对象是否创建成功
if(NULL==_socket){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"创建套接字失败" delegate:nil cancelButtonTitle:@"关闭" otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
//sockaddr_in配置socket的地址信息
structsockaddr_inaddr4;
//初始化addr4的内存
//mem == memory内存
//sizeof(addr4)计算addr4在内存中占用空间(几个字节)
memset(&addr4, 0,sizeof(addr4));
//让内存对齐按一个字节对齐防止出现片段编码
bzero(&addr4,sizeof(addr4));
//赋值一下结构体本身的长度
addr4.sin_len = sizeof(addr4);
//AF_INET ipv4
addr4.sin_family = AF_INET;
//将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
addr4.sin_port = htons(SOCKET_PORT);
//将一个点分十进制的IP转换成一个长整数型数INET_ADDR()。
//[SOCKET_IP UTF8String] OC字符串转C字符串
addr4.sin_addr.s_addr = inet_addr([SOCKET_IPUTF8String]);
//把sockaddr_in结构体中的地址转换为Data,CFDataRef == NSData
CFDataRefaddress = CFDataCreate(kCFAllocatorDefault, (UInt8*)&addr4,sizeof(addr4));
// address CFDataRef类型的包含上面socket的远程地址的对象
CFSocketConnectToAddress(_socket, address, 30);
//获取当前线程的消息循环
CFRunLoopRefcfrl = CFRunLoopGetCurrent();
//创建一个消息源
CFRunLoopSourceRefsource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
//把消息源添加到消息循环中
CFRunLoopAddSource(cfrl, source,kCFRunLoopCommonModes);
//CFXXXXXRef创建的对象需要释放
CFRelease(source);
CFRelease(address);
}
//static类似于OC的加号方法copy过来进行修改
staticvoidTCPServerConnectCallBack(CFSocketRefsocket,CFSocketCallBackTypetype,CFDataRefaddress,constvoid*error,void*info){
NSLog(@"连接到socket服务器");
//当socket为kCFSocketConnectCallBack时,失败时回调会返回一个错误代码指针(data),其他情况返回NULL
if(error != NULL){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:@"连接失败" delegate:nil cancelButtonTitle:@"关闭" otherButtonTitles:nil];
[alert show];
[alert release];
return;
}
//因为self调用不了-号方法所以就创建一个self的对象
//info和CFSocketContext CTX = {0,self,NULL,NULL,NULL};中的self对应
ZYViewController *infoObject = (ZYViewController *)info;
//其效果与NSThread的detachNewThreadSelector:toTarget:withObject:是一样的。
//开启一个分线程,调用readStream这个方法,这个方法是在分线程中进行的。
[info ObjectperformSelectorInBackground:@selector(readStream) withObject:nil];
[info ObjectuserLogin];
//测试用的
//[info Object httpTest];
}
- (void)httpTest {
NSString *str =@"GET /SocketHttpTest/TestServlet?name=wwwwww&password=12dddssd HTTP/1.1\r\n"
@"Host:localhost\r\n"
@"connection:close\r\n\r\n"
@"\r\n";
constchar *s2 = [strUTF8String];
send(CFSocketGetNative(_socket),s2,[strlength], 0);
}
//解析终端登录包
- (void)userLogin {
//网络字节序,大端模式
//一共需要23个字节(包含长度本身2个)
chardata[23];
data[0] = (21 >> 8) & 0x000000ff;//0
data[1] = 21 & 0x000000ff;//21
//使用配置文件宏定义好的内容
data[2] =CLIENT_SIDE;
data[3] =LOGIN_COMMAND;
//软件版本全为0测试
for(inti = 4; i < 12; i++) {
data[i] = 0;
}
//设备序列号测试
for(inti = 12; i < 23; i++) {
data[i] = 't';
}
//发送请求
send(CFSocketGetNative(_socket),&data,sizeof(data), 0);
}
- (void)readStream {
//将来里面放的是‘包长度(2)、哪个端(1)、命令号(1)’4个
charheader[4];
//一直读取服务器返回的包
//recv方法参数1.从哪个连接中读,2.读到的数据存到内存中的地址,3.读多长的数据
while(recv(CFSocketGetNative(_socket),&header,sizeof(header), 0)) {
//哪个端第三个
Byteside = header[2];
//命令号第四个
Bytecommand = header[3];
if(command == LOGIN_RESPONSE_COMMAND&& side == 'S') {
NSLog(@"登陆返回了");
//得到包的长度(除去长度字段本身)或运算(转换为十进制结果)
intlength = ( header[0] << 8 ) | header[1];
//这个字节数组放的是‘当前时间、结果代码、原因’
charloginResponse[length - 2];
//除去前四个字节后继续解析读取
if(recv(CFSocketGetNative(_socket),&loginResponse,sizeof(loginResponse), 0)) {
NSLog(@"年____ %d",loginResponse[0]);
NSLog(@"月____ %d",loginResponse[1]);
NSLog(@"日____ %d",loginResponse[2]);
NSLog(@"时____ %d",loginResponse[3]);
NSLog(@"分____ %d",loginResponse[4]);
NSLog(@"秒____ %d",loginResponse[5]);
BOOLfailed = loginResponse[6];
NSLog(@"登陆是否失败%d",failed);
}
} else if(command == 0x80 && side == 'S') {
NSLog(@"收到心跳包");
//if外边已经读取过4个了,只用读取剩下的两个
charinterval[2];
if(recv(CFSocketGetNative(_socket),&interval,sizeof(interval), 0)) {
//心跳时间是多字节往左移8位
int nextInterVal = (interval[0] << 8) | interval[1];
NSLog(@"下次心跳时间%d",nextInterVal);
chardata[4];
//2-->0000 0010考虑大端模式高低位互换0--0 1--2
data[0] = 0;
data[1] = 2;
data[2] = 'E';
data[3] = 0x00;
//发送数据
send(CFSocketGetNative(_socket),&data,4, 0);
}
}
}
}
@end