本文共 12168 字,大约阅读时间需要 40 分钟。
socket编程的几中方法:
C语言底层socket----BSD socket--CFSocket可以代替他的所有工作;----C语言
CFSocket;------c语言;
CFStream输入输出流socket通信;----OC的Corefoundation框架中
GCDSocket;-------第三方的框架
1.CFSocket;------c语言;
//lb
IPV4:
//struct sockaddr_in {
// __uint8_t sin_len;
// sa_family_t sin_family;//iPv4地址族
// in_port_t sin_port; //端口号
// struct in_addr sin_addr;//IPV4 address
// char sin_zero[8];
//};
lb
IPV6:
// struct sockaddr_in6 {
// __uint8_t sin6_len; /* length of this struct(sa_family_t) */
// sa_family_t sin6_family;/* AF_INET6 (sa_family_t) */
// in_port_t sin6_port; /* Transport layer port # (in_port_t) */
// __uint32_t sin6_flowinfo;/* IP6 flow information */
// struct in6_addr sin6_addr;/* IP6 address */
// __uint32_t sin6_scope_id;/* scope zone index */
// };
lb
getAddrinfo的参数:
// struct addrinfo {
// int ai_flags;/* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
// int ai_family;/* PF_xxx */地址族,
// int ai_socktype;/* SOCK_xxx */套接字类型
// int ai_protocol;/* 0 or IPPROTO_xxx for IPv4 and IPv6 */协议类型
// socklen_t ai_addrlen; /* length of ai_addr */指向缓冲区的字节数
// char *ai_canonname;/* canonical name for hostname */主机的规范化名称
// struct sockaddr *ai_addr;/* binary address */指向sockaddr结构的指针。getaddrinfo返回的每个addrinfo结构内的ai_addr都指向一个filled-in套接字地址结构。
// struct addrinfo *ai_next;/* next structure in linked list */指向链表中下一个addrinfo结构的指针,如果是链表的最后一个addrinfo结构,则ai_next为NULL。
// };
lb
/**转换IPV4和IPV6
int getaddrinfo(
const char* nodename,
const char* servname,
const struct addrinfo* hints,
struct addrinfo** res
);
* nodename:节点名可以是主机名,也可以是数字地址。(IPV4的10进点分,或是IPV6的16进制)
* servname:包含十进制数的端口号或服务名如(ftp,http)
* hints:是一个空指针或指向一个addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索。
* res:存放返回addrinfo结构链表的指针,指向由一个或多个addrinfo结构体组成的链表,包含了主机的响应信息
* 返回值:成功返回0,失败返回非零的 sockets error code
*/
lb
//建立连接(IPV4和IPV6兼容)
- (void)createConnect
{
/***********************************/lb
struct addrinfo hints, *res, *res0;
int error, s;
const char *cause =NULL;
constchar *ipv4_or_ipv6_str ="60.173.247.137";//or IPv6 address string is well /ip地址
// const char *ipv4_or_ipv6_str = "";
NSUInteger port =9001;//port of connecting server/端口号
memset(&hints,0,sizeof(hints));//分配一个hints结构体,把它清零后填写需要的字段,再调用getaddrinfo,然后遍历一个链表逐个尝试每个返回地址。
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(ipv4_or_ipv6_str, NULL, &hints, &res);//函数的返回值:成功返回0,失败返回非零的 sockets error code
if (error)//非零则失败
{
errx(1,"%s",gai_strerror(error));
/*NOTREACHED*/
}
s = -1;
for (res = res0; res; res = res->ai_next)
{
s = socket(res->ai_family,
res->ai_socktype,
res->ai_protocol);//返回值:非负描述符成功,返回一个新的套接字描述,出错返回-1
close(s);/很关键,释放占用的socket描述//
NSLog(@"ssssssss%d",s);
//socket上下文
CFSocketContext sockContext = {
0,self,
NULL,
NULL,
NULL};
//创建socket
m_pSocket =CFSocketCreate(kCFAllocatorDefault,
res->ai_family,//AF_UNSPEC不限,PF_INET,PF_INET6
res->ai_socktype,
res->ai_protocol,
kCFSocketConnectCallBack,
TCPClientConnectCallBack, //连接后的回调函数
&sockContext
);
if (s < 0)
{
cause = "socket";
continue;
}
switch(res->ai_addr->sa_family)//是IPV4还是IPV6
{
case AF_INET://IPV4
{
struct sockaddr_in *v4sa = (struct sockaddr_in *)res->ai_addr;
v4sa->sin_port = htons(port);
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v4sa,sizeof(v4sa));
// 建立连接
CFSocketConnectToAddress(m_pSocket,
address,
-1 //超时
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent();
CFRunLoopSourceRef sourceRef =CFSocketCreateRunLoopSource(kCFAllocatorDefault,m_pSocket,0);
CFRunLoopAddSource(cRunRef,
sourceRef,
kCFRunLoopCommonModes
);
CFRelease(sourceRef);
CFRelease(address);
NSLog(@"连接成功1");
}
break;
case AF_INET6://IPV6
{
struct sockaddr_in6 *v6sa = (struct sockaddr_in6 *)res->ai_addr;
v6sa->sin6_port = htons(port);
CFDataRef address6 = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v6sa,sizeof(v6sa));
//建立连接IPV6
CFSocketConnectToAddress(m_pSocket,
address6,
-1
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent();
CFRunLoopSourceRef sourceRef =CFSocketCreateRunLoopSource(kCFAllocatorDefault,m_pSocket,0);
CFRunLoopAddSource(cRunRef,
sourceRef,
kCFRunLoopCommonModes
);
CFRelease(sourceRef);
CFRelease(address6);
NSLog(@"连接成功2");
}
break;
}
// 函数说明:connect()用来将参数sockfd的socket连至参数serv_addr指定的网络地址. 结构sockaddr请参考bind().参数addrlen为sockaddr的结构长度.
//
// 返回值:成功则返回0,失败返回-1,错误原因存于errno中.
//
// 错误代码:
// 1、EBADF参数sockfd非合法socket处理代码
// 2、EFAULT参数serv_addr指针指向无法存取的内存空间
// 3、ENOTSOCK参数sockfd为一文件描述词,非socket.
// 4、EISCONN参数sockfd的socket已是连线状态
// 5、 ETIMEDOUT企图连线的操作超过限定时间仍未有响应.
// 6、ENETUNREACH无法传送数据包至指定的主机.
// 7、EAFNOSUPPORT sockaddr结构的sa_family不正确.
// 8、EALREADY socket为不可阻断且先前的连线操作还未完成.
if (connect(s, res->ai_addr, res->ai_addrlen) <0)//连接失败的处理
{
cause = "connect";
close(s);//关闭套接字描述
s = -1;
continue;
}
break; /* okay we got one *///连接成功就跳出循环
}
if (s <0)//socket描述失败
{
err(1,"%s", cause);
/*NOTREACHED*/
}
else//socket描述成功
{
printf("描述成功connected");
}
freeaddrinfo(res);
//}
**********************************************lb
//=============================================================lb
IPV4:连接
// // 创建socket上下文
// CFSocketContext sockContext = {0,
// self,
// NULL,
// NULL,
// NULL};
//
// //创建socket
// m_pSocket = CFSocketCreate(kCFAllocatorDefault,
// AF_UNSPEC,//AF_UNSPEC不限,PF_INET,PF_INET6
// SOCK_STREAM,
// IPPROTO_TCP,
// kCFSocketConnectCallBack,
// TCPClientConnectCallBack, //连接后的回调函数
// &sockContext
// );
// if (m_pSocket != NULL)
// {
// struct sockaddr_in addr4; // IPV4
// memset(&addr4, 0, sizeof(addr4));
// addr4.sin_len = sizeof(addr4);
// addr4.sin_family = AF_INET;//协议族
// addr4.sin_port = htons(9001);//端口号,高低位转换
// addr4.sin_addr.s_addr = inet_addr([serverIp UTF8String]);//ip地址
//
// CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
//
// //建立连接
// CFSocketConnectToAddress(m_pSocket,
// address,
// -1 //超时
// );
//
// CFRunLoopRef cRunRef = CFRunLoopGetCurrent();
//
// CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, m_pSocket, 0);
// CFRunLoopAddSource(cRunRef,
// sourceRef,
// kCFRunLoopCommonModes
// );
// CFRelease(sourceRef);
// CFRelease(address);
//===================================================
// }
=================================
socket网络的层级:
#import "ViewController.h"
@interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>{
NSInputStream *_inputStream;//对应输入流
NSOutputStream *_outputStream;//对应输出流
}
@property (weak, nonatomic) IBOutletNSLayoutConstraint *inputViewConstraint;
@property (weak, nonatomic) IBOutletUITableView *tableView;
@property (nonatomic,strong)NSMutableArray *chatMsgs;//聊天消息数组
@end
@implementation ViewController
-(NSMutableArray *)chatMsgs{
if (!_chatMsgs) {
_chatMsgs = [NSMutableArrayarray];
}
return_chatMsgs;
}
- (void)viewDidLoad {
[superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 2.收发数据
// 做一个聊天
// 1.用户登录
// 2.收发数据
// 监听键盘
[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbFrmWillChange:)name:UIKeyboardWillChangeFrameNotificationobject:nil];
}
-(void)kbFrmWillChange:(NSNotification *)noti{
NSLog(@"%@",noti.userInfo);
// 获取窗口的高度
CGFloat windowH = [UIScreenmainScreen].bounds.size.height;
// 键盘结束的Frm
CGRect kbEndFrm = [noti.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue];
// 获取键盘结束的y值
CGFloat kbEndY = kbEndFrm.origin.y;
self.inputViewConstraint.constant = windowH - kbEndY;
}
//NSStreamDelegate---------返回输入输出流的状态;
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
NSLog(@"%@",[NSThreadcurrentThread]);
// NSStreamEventOpenCompleted = 1UL << 0,//输入输出流打开完成
// NSStreamEventHasBytesAvailable = 1UL << 1,//有字节可读
// NSStreamEventHasSpaceAvailable = 1UL << 2,//可以发放字节
// NSStreamEventErrorOccurred = 1UL << 3,// 连接出现错误
// NSStreamEventEndEncountered = 1UL << 4// 连接结束
switch (eventCode) {
caseNSStreamEventOpenCompleted:
NSLog(@"输入输出流打开完成");
break;
caseNSStreamEventHasBytesAvailable:
NSLog(@"有字节可读");
[self readData];
break;
caseNSStreamEventHasSpaceAvailable:
NSLog(@"可以发送字节");
break;
caseNSStreamEventErrorOccurred:
NSLog(@"连接出现错误");
break;
caseNSStreamEventEndEncountered:
NSLog(@"连接结束");
// 关闭输入输出流
[_inputStream close];
[_outputStream close];
// 从主运行循环移除
[_inputStream removeFromRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];
[_outputStream removeFromRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];
break;
default:
break;
}
}
- (IBAction)connectToHost:(id)sender {
// 1.建立连接
NSString *host = @"127.0.0.1";
int port = 12345;
// 定义C语言输入输出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridgeCFStringRef)host, port, &readStream, &writeStream);
// 把C语言的输入输出流转化成OC对象
_inputStream = (__bridgeNSInputStream *)(readStream);
_outputStream = (__bridgeNSOutputStream *)(writeStream);
// 设置代理
_inputStream.delegate =self;
_outputStream.delegate =self;
// 把输入输入流添加到主运行循环
// 不添加主运行循环代理有可能不工作
[_inputStream scheduleInRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];
// 打开输入输出流
[_inputStream open];
[_outputStream open];
}
- (IBAction)loginBtnClick:(id)sender {
// 登录
// 发送用户名和密码
// 在这里做的时候,只发用户名,密码就不用发送
// 如果要登录,发送的数据格式为 "iam:zhangsan";
// 如果要发送聊天消息,数据格式为 "msg:did you have dinner";
//登录的指令
NSString *loginStr = @"iam:zhangsan";
//把Str转成NSData
NSData *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding];
//输出缓冲区数据
[_outputStream write:data.bytes maxLength:data.length];
}
#pragma mark 读了服务器返回的数据
-(void)readData{
//建立一个缓冲区可以放1024个字节
uint8_t buf[1024];
// 返回实际装的字节数,读取缓冲区数据
NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];
// 把字节数组转化成字符串
NSData *data = [NSDatadataWithBytes:buflength:len];
// 从服务器接收到的数据
NSString *recStr = [[NSStringalloc]initWithData:dataencoding:NSUTF8StringEncoding];
NSLog(@"%@",recStr);
[selfreloadDataWithText:recStr];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSString *text = textField.text;
NSLog(@"%@",text);
// 聊天信息
NSString *msgStr = [NSString stringWithFormat:@"msg:%@",text];
//把Str转成NSData
NSData *data = [msgStr dataUsingEncoding:NSUTF8StringEncoding];
// 刷新表格
[selfreloadDataWithText:msgStr];
// 发送数据
[_outputStream write:data.bytes maxLength:data.length];
// 发送完数据,清空textField
textField.text = nil;
return YES;
}
-(void)reloadDataWithText:(NSString *)text{
[self.chatMsgsaddObject:text];
[self.tableViewreloadData];
// 数据多,应该往上滚动
NSIndexPath *lastPath = [NSIndexPath indexPathForRow:self.chatMsgs.count -1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastPathat ScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
#pragma mark 表格的数据源
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.chatMsgs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID =@"Cell";
UITableViewCell *cell = [tableViewdequeueReusableCellWithIdentifier:ID];
cell.textLabel.text =self.chatMsgs[indexPath.row];
return cell;
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
[self.view endEditing:YES];
}
@end