iOS 使用 socket 实现即时通信示例(非第三方库)
程序员文章站
2023-12-20 16:55:10
其实写这个socket一开始我是拒绝的。
因为大家学c 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底...
其实写这个socket一开始我是拒绝的。
因为大家学c 语言和linux基础时肯定都有接触,客户端和服务端的通信也都了解过,加上现在很多开放的第三方库都不需要我们来操作底层的通信。
但是!还是想写。底层的东西最好了解下。
效果
由于5m的上传限制gif可能看不清 我再截两张图吧
服务器
客户端a
客户端b
模型图
分析
由上图可以了解到服务器和客户端需要做哪些工作
服务器
抽象一点分为:
1.创建线程等待接收客户端的连接
2.接收并解析客户端发来的消息
3.给客户端发送消息
具体一点:
1.创建socket. 绑定端口.开始监听.
2.创建线程.等待接收客户端连接.
3.接收客户端发来的消息
4.解析消息内容
a.设置用户名
b.发送消息给指定客户端
客户端
抽象一点分为:
1.连接服务器
2.给服务器发送消息
3.接收服务器消息
4.解析消息内容
具体一点:
1.创建socket.绑定端口.连接服务器
2.发送消息
a.设置用户名
b.给指定用户发消息:按服务器格式拼接字符串
3.接收消息
a.普通消息
b.用户列表:保存至用户列表
ui方面
服务器:其实不用什么ui放个控件展示下日志就是了
客户端:比较简单,一个俗套聊天室的界面,直接storyboard里拖拖控件设置约束了
demo而已别太当真
代码部分
服务器
要使用scoket需要引用这三个头文件
#include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h>
只有一个model,用来绑定用户名和socket
@interface clientmodel : nsobject @property(nonatomic,assign)int clientsocket; @property(nonatomic,copy)nsstring *clientname; @end
只有一个文件全给你
#import "viewcontroller.h" #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #import "clientmodel.h" static int const kmaxconnectcount = 5; @interface viewcontroller() @property (weak) iboutlet nstextfield *textfield; //@property (nonatomic,assign)int client_socket; //客户端socket @property (unsafe_unretained) iboutlet nstextview *textview; @property (nonatomic,strong)nsmutablearray *clientarray; @property (nonatomic,strong)nsmutablearray *clientnamearray; @end @implementation viewcontroller - (nsmutablearray *)clientarray { if (!_clientarray) { _clientarray = [nsmutablearray array]; } return _clientarray; } - (nsmutablearray *)clientnamearray { if (!_clientnamearray) { _clientnamearray = [nsmutablearray array]; } return _clientnamearray; } - (void)viewdidload { [super viewdidload]; // do any additional setup after loading the view. //创建socket int server_socket = socket(af_inet, sock_stream, 0); if (server_socket == -1) { nslog(@"创建失败"); [self showlogswithstring:@"socket创建失败"]; }else{ //绑定地址和端口 struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = af_inet; server_addr.sin_port = htons(1234); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bzero(&(server_addr.sin_zero), 8); int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (bind_result == -1) { nslog(@"绑定端口失败"); [self showlogswithstring:@"绑定端口失败"]; }else{ if (listen(server_socket, kmaxconnectcount)==-1) { nslog(@"监听失败"); [self showlogswithstring:@"监听失败"]; }else{ for (int i = 0; i < kmaxconnectcount; i++) { //接受客户端的链接 [self acceptclientwithserversocket:server_socket]; } } } } } - (void)setrepresentedobject:(id)representedobject { [super setrepresentedobject:representedobject]; // update the view, if already loaded. } //创建线程接受客户端 -(void)acceptclientwithserversocket:(int)server_socket{ struct sockaddr_in client_address; socklen_t address_len; dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_default, 0); dispatch_async(queue, ^{ //创建新的socket while (1) { int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len ); if (client_socket == -1) { [self showlogswithstring:@"接受客户端链接失败"]; nslog(@"接受客户端链接失败"); }else{ nsstring *acceptinfo = [nsstring stringwithformat:@"客户端 in,socket:%d",client_socket]; [self showlogswithstring:acceptinfo]; //接受客户端数据 [self recvfromclinetwithsocket:client_socket]; } } }); } //接受客户端数据 - (void)recvfromclinetwithsocket:(int)client_socket{ while (1) { //接受客户端传来的数据 char buf[1024] = {0}; long ireturn = recv(client_socket, buf, 1024, 0); if (ireturn>0) { nslog(@"客户端来消息了"); nsstring *str = [nsstring stringwithcstring:buf encoding:nsutf8stringencoding]; [self showlogswithstring:[nsstring stringwithformat:@"客户端来消息了:%@",str]]; [self checkrecvstr:str andclientsocket:client_socket]; }else if (ireturn == -1){ nslog(@"读取消息失败"); [self showlogswithstring:@"读取消息失败"]; break; }else if (ireturn == 0){ nslog(@"客户端走了"); [self showlogswithstring:[nsstring stringwithformat:@"客户端 out socket:%d",client_socket]]; nsmutablearray *array = [nsmutablearray arraywitharray:self.clientarray]; for (clientmodel *model in array) { if (model.clientsocket == client_socket) { [self.clientnamearray removeobject:model.clientname]; [self.clientarray removeobject:model]; } } close(client_socket); break; } } } //检查接受到的字符串 - (void)checkrecvstr:(nsstring*)str andclientsocket:(int)socket{ if ([str hasprefix:@"name:"]) { nsstring *name = [str substringfromindex:5]; clientmodel *model = [[clientmodel alloc] init]; model.clientsocket = socket; model.clientname = name; if (self.clientarray.count > 0) { int flag = 999; //用户名不能相同 int i = 0; for (clientmodel *client in self.clientarray) { //改名 if (client.clientsocket == socket) { nsstring *oldname = self.clientnamearray[i]; self.clientnamearray[i] = name; self.clientarray[i] = model; for (clientmodel *oldclient in self.clientarray) { [self sendmsg:[nsstring stringwithformat:@"%@ 改名 %@",oldname,name] toclient:oldclient.clientsocket]; [self showlogswithstring:[nsstring stringwithformat:@"%@ 改名 %@",oldname,name]]; nsstring *list = [self.clientnamearray componentsjoinedbystring:@","]; //向客户端推送当前在线列表 [self sendmsg:[nsstring stringwithformat:@"list:%@",list] toclient:oldclient.clientsocket]; } flag = 2; }else{ if ([client.clientname isequaltostring:model.clientname]) { //用户名已存在 flag = 1; break; } } i++; } if (flag != 1 & flag != 2) { [self.clientarray addobject:model]; [self.clientnamearray addobject:model.clientname]; //向客户端推送当前在线列表 for (clientmodel *client in self.clientarray) { [self sendmsg:[nsstring stringwithformat:@"%@,上线了",name] toclient:client.clientsocket]; nsstring *list = [self.clientnamearray componentsjoinedbystring:@","]; //向客户端推送当前在线列表 [self sendmsg:[nsstring stringwithformat:@"list:%@",list] toclient:client.clientsocket]; } //给当前客户端发送一条欢迎信息 nsstring *msg = [nsstring stringwithformat:@"welcome %@ !",name]; [self sendmsg:msg toclient:socket]; [self showlogswithstring:msg]; }else if (flag == 1){ [self sendmsg:@"注册用户名失败,用户名已经存在,请重新设置用户名" toclient:socket]; [self showlogswithstring:[nsstring stringwithformat:@"socket %d 注册用户名失败,设置的用户名已经存在",socket]]; for (clientmodel *model in self.clientarray) { [name isequaltostring:model.clientname]; } } }else{ [self.clientarray addobject:model]; [self.clientnamearray addobject:model.clientname]; //向客户端推送当前在线列表 //给当前客户端发送一条欢迎信息 nsstring *msg = [nsstring stringwithformat:@"welcome %@ !",name]; [self sendmsg:msg toclient:socket]; [self showlogswithstring:msg]; nsstring *list = [self.clientnamearray componentsjoinedbystring:@","]; //向客户端推送当前在线列表 [self sendmsg:[nsstring stringwithformat:@"list:%@",list] toclient:socket]; } } //给某人发消息 else if ([str hasprefix:@"to:"]){ nsrange namerange = [str rangeofstring:@"*"]; nsstring *name = [str substringwithrange:nsmakerange(3, namerange.location-3)]; nsstring *content = [str substringfromindex:namerange.location+1]; nsstring *fromclientname; //找出发送者 for (clientmodel *model in self.clientarray) { if (socket == model.clientsocket) { fromclientname = model.clientname; break; } } //给目标发送信息 for (clientmodel *model in self.clientarray) { if ([name isequaltostring:model.clientname]) { nsstring *msg = [nsstring stringwithformat:@"%@ to you\n%@",fromclientname,content]; [self sendmsg:msg toclient:model.clientsocket]; [self showlogswithstring:[nsstring stringwithformat:@"%@ 发送给 %@ 内容是:%@",fromclientname,name,content]]; break; } } } } //给客户端发送信息 - (void)sendmsg:(nsstring*)msg toclient:(int)socket{ char *buf[1024] = {0}; const char *p1 = (char*)buf; p1 = [msg cstringusingencoding:nsutf8stringencoding]; send(socket, p1, 1024, 0); } //在界面上显示日志 - (void)showlogswithstring:(nsstring*)str { dispatch_async(dispatch_get_main_queue(), ^{ nsstring *newstr = [nsstring stringwithformat:@"\n%@",str]; self.textview.string = [self.textview.string stringbyappendingstring:newstr]; }); } @end
客户端
由于客户端设计的就比较简单,所以代码量也很少,全给你.
#import "viewcontroller.h" #include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> @interface viewcontroller ()<uitableviewdelegate,uitableviewdatasource> //服务器socket @property (nonatomic,assign)int server_socket; //ui @property (weak, nonatomic) iboutlet uitextfield *usernamefield; @property (weak, nonatomic) iboutlet uitextview *chatview; @property (weak, nonatomic) iboutlet uitextfield *msgfield; @property (weak, nonatomic) iboutlet uilabel *toname; @property (weak, nonatomic) iboutlet uiview *onlineuserview; @property (nonatomic,strong)uitableview *onlinetable; //user列表 @property (nonatomic,strong)nsmutablearray *userarray; @end @implementation viewcontroller - (nsmutablearray *)userarray { if (!_userarray) { _userarray = [nsmutablearray array]; } return _userarray; } - (void)viewdidload { [super viewdidload]; [self.usernamefield becomefirstresponder]; self.usernamefield.text = @""; self.msgfield.text = @""; //添加table用户列表 self.onlinetable = [[uitableview alloc] initwithframe:self.onlineuserview.frame style:uitableviewstyleplain]; self.onlinetable.delegate = self; self.onlinetable.datasource = self; [self.view addsubview:self.onlinetable]; int server_socket = socket(af_inet, sock_stream, 0); if (server_socket == -1) { nslog(@"创建失败"); }else{ //绑定地址和端口 struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = af_inet; server_addr.sin_port = htons(1234); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bzero(&(server_addr.sin_zero), 8); //接受客户端的链接 dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_default, 0); dispatch_async(queue, ^{ //创建新的socket int aresult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)); if (aresult == -1) { nslog(@"链接失败"); }else{ self.server_socket = server_socket; [self acceptfromserver]; } }); } } //从服务端接受消息 - (void)acceptfromserver{ while (1) { //接受服务器传来的数据 char buf[1024]; long ireturn = recv(self.server_socket, buf, 1024, 0); if (ireturn>0) { nsstring *str = [nsstring stringwithcstring:buf encoding:nsutf8stringencoding]; //筛选前缀 if ([str hasprefix:@"list:"]) { nsstring *arraystr = [str substringfromindex:5]; nsarray *list = [arraystr componentsseparatedbystring:@","]; self.userarray = [nsmutablearray arraywitharray:list]; dispatch_async(dispatch_get_main_queue(), ^{ [self.onlinetable reloaddata]; }); nslog(@"当前在线用户列表:%@",arraystr); }else{ //回到主线程 界面上显示内容 [self showlogswithstring:str]; } }else if (ireturn == -1){ nslog(@"接受失败-1"); break; } } } //在界面上显示日志 - (void)showlogswithstring:(nsstring*)str { dispatch_async(dispatch_get_main_queue(), ^{ nsstring *newstr = [nsstring stringwithformat:@"\n%@",str]; self.chatview.text = [self.chatview.text stringbyappendingstring:newstr]; }); } - (void)didreceivememorywarning { [super didreceivememorywarning]; // dispose of any resources that can be recreated. } //设置用户名 - (ibaction)clicksetusername:(id)sender { nsstring *msg = [nsstring stringwithformat:@"name:%@",self.usernamefield.text] ; [self sendmsg:msg]; // [self showlogswithstring:msg]; [self.msgfield becomefirstresponder]; } //发送信息 - (ibaction)clicksendmsg:(id)sender { if ([self.msgfield.text isequaltostring:@""] || ![self.userarray containsobject:self.usernamefield.text] || [self.toname.text isequaltostring:self.usernamefield.text]) { [self showlogswithstring:@"请设置用户名、检查发送对象、消息不能为空"]; return; } nsstring *msg = [nsstring stringwithformat:@"to:%@*%@",self.toname.text,self.msgfield.text]; [self sendmsg:msg]; nsstring *displaymsg = [nsstring stringwithformat:@"to:%@\n%@",self.toname.text,self.msgfield.text]; [self showlogswithstring:displaymsg]; self.msgfield.text = @""; } //给客户端发送信息 - (void)sendmsg:(nsstring*)msg { char *buf[1024] = {0}; const char *p1 = (char*)buf; p1 = [msg cstringusingencoding:nsutf8stringencoding]; send(self.server_socket, p1, 1024, 0); } #pragma mark - tableviewdelegate & datasource - (nsinteger)tableview:(uitableview *)tableview numberofrowsinsection:(nsinteger)section{ return self.userarray.count; } - (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath{ static nsstring *cellid = @"onlinetableviewcellid"; uitableviewcell *cell = [tableview dequeuereusablecellwithidentifier:cellid]; if (cell == nil) { cell = [[uitableviewcell alloc] initwithstyle:uitableviewcellstyledefault reuseidentifier:cellid]; }else{ nslog(@"cell重用了"); } cell.textlabel.text = self.userarray[indexpath.row]; return cell; } //点击cell - (void)tableview:(uitableview *)tableview didselectrowatindexpath:(nsindexpath *)indexpath{ self.toname.text = self.userarray[indexpath.row]; [self.msgfield becomefirstresponder]; } @end
demo地址:imsocketdemo_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。