English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
이전에 App 간의 다섯 가지 통신 방법에 대해 소개한 기사를 보았습니다. 그 중에는 URL Scheme, Keychain, UIPastedboard, UIDocumentInteractionController, 그리고 로컬 통신을 위해 소켓을 사용하는 것이 있습니다. 이전에4모두 사용해 봤고, 상대적으로 간단합니다. 몇 줄의 코드로 끝납니다. 마지막 종류는 이전에 사용한 적이 없었습니다(제가 초보자라는 것을 용서해 주세요), 그래서 오늘은 시도해 보았습니다. 여기서 기록해 두고 여러분과 공유합니다.
말도 많지 않게 시작해 보겠습니다.
먼저, 그 원리에 대해 설명해 보겠습니다. 사실 간단합니다. 하나의 App이 로컬 포트에서 TCP의 bind와 listen을 수행하고, 또 다른 App이 동일한 포트에서 connect를 수행하면 정상적인 TCP 연결이 형성됩니다. 전달하고 싶은 데이터는 무엇이든 전달할 수 있습니다. 아래서 먼저 서버 측을 생성해 보겠습니다.
1소켓 함수를 사용하여 최초로 소켓을 생성합니다.
/* * 소켓이 int 값을 반환합니다.-1소켓 생성 실패로 * 첫 번째 매개변수는 프로토콜 가족을 지정합니다/도메인, 일반적으로 AF_INET(IPV4) AF_INET6) IPV6) AF_LOCAL * 두 번째 매개변수는 소켓 인터페이스 유형을 지정합니다. 예를 들어 SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET 등 * 세 번째 매개변수는 해당 전송 프로토콜을 지정합니다. 예를 들어 TCP/UDP와 같이 일반적으로 0을 설정하여 기본 값 사용 */ int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1){ close(sock); NSLog(@"socket error : %d", sock);<br> return; } /* * 소켓이 int 값을 반환합니다.-1소켓 생성 실패로 * 첫 번째 매개변수는 프로토콜 가족을 지정합니다/도메인, 일반적으로 AF_INET(IPV4) AF_INET6) IPV6) AF_LOCAL * 두 번째 매개변수는 소켓 인터페이스 유형을 지정합니다. 예를 들어 SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET 등 * 세 번째 매개변수는 해당 전송 프로토콜을 지정합니다. 예를 들어 TCP/UDP와 같이 일반적으로 0을 설정하여 기본 값 사용 */ int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1){ close(sock); NSLog(@"socket error : %d", sock);<br> return; }
2주소와 포트 번호를 바인딩
// 주소 구조체 데이터입니다. ip와 포트 번호를 기록합니다 struct sockaddr_in sockAddr; // 사용할 프로토콜을 선언합니다 sockAddr.sin_family = AF_INET; // 컴퓨터의 ip를 가져오고 char 타입으로 변환합니다 const char *ip = [[self getIPAddress] cStringUsingEncoding:NSASCIIStringEncoding]; // ip를 구조체에 할당합니다. inet_addr() 함수는 점 분할 십진수 IP를 장整数형으로 변환합니다 sockAddr.sin_addr.s_addr = inet_addr(ip); // 포트 번호를 설정합니다. htons()는 정수 변수를 호스트 비트 순서에서 네트워크 비트 순서로 변환합니다 sockAddr.sin_port = htons(12345; /* * bind 함수는 소켓을 주소와 연결하는 데 사용되며, int 값을 반환합니다.-1为失败 * 첫 번째 매개변수는 소켓을 지정합니다. 이는 socket 함수 호출로 반환된 소켓입니다 * 두 번째 매개변수는 지정된 주소입니다 * 세 번째 매개변수는 주소 데이터의 크기입니다 */ int bd = bind(sock, (struct sockaddr *) &sockAddr, sizeof(sockAddr)); if(bd == -1){ close(sock); NSLog(@"bind error : %d", bd); return; }
3주소를監听
/* * listen 함수는 적극적인 연결 소켓 인터페이스를 연결된 인터페이스로 변환하여 다른 프로세스의 요청을 받을 수 있도록 하고, int 값을 반환합니다.-1为失败 * 第一个参数是之前socket函数返回的套接字 * 第二个参数可以理解为连接的最大限制 */ int ls = listen(sock,2,0); if(ls == -1){ close(sock); NSLog(@"listen error : %d",ls); return; }
4、下面就是等待客户端的连接,使用accept()(由于accept函数会阻塞线程,在等待连接的过程中会一直卡着,所以建议将其放在子线程里面)
// 1.开启一个子线程 NSTread *recvThread = [[NSThread alloc] initwithTarget:self selector:@selector(recvData) object: nil]; [recvThread start]; - (void)recvData{ // 2.等待客户端连接 // 声明一个地址结构体,用于后面接收客户端返回的地址 struct sockaddr_in recvAddr; // 地址大小 socklen_t recv_size = sizeof(struct sockaddr_in); /* * accept()函数在连接成功后会返回一个新的套接字(self.newSock),用于之后和这个客户端之前收发数据 * 第一个参数为之前监听的套接字,之前是局部变量,现在需要改为全局的 * 第二个参数是一个结果参数,它用来接收一个返回值,这个返回值指定客户端的地址 * 第三个参数也是一个结果参数,它用来接收recvAddr结构体的代销,指明其所占的字节数 */ self.newSock = accept(self.sock,(struct sockaddr *) &recvAddr, &recv_size); // 3.来到这里就代表已经连接到一个新的客户端,下面就可以进行收发数据了,主要用到了send()和recv()函数 ssize_t bytesRecv = -1; // 返回数据字节大小 char recvData[128] = ""; // 返回数据缓存区 // 如果一端断开连接,recv就会马上返回,bytesrecv等于0,然后while循环就会一直执行,所以判断等于0是跳出去 while(1){ bytesRecv = recv(self.newSocket,recvData,128,0); // recvData为收到的数据 if(bytesRecv == 0){ break; } } }
5、发送数据
- (void)sendMessage{ char sendData[32]= "hello client"; ssize_t size_t = send(self.newSocket, sendData, strlen(sendData), 0); }
客户端那边就主要分为:创建套接字,根据ip和端口号获取服务端的主机地址,然后再连接,连接成功过后就能够向服务端收发数据了,下面我们看代码。
1、和服务端一样用socket函数创建套接字
int sock = socket(AF_INET, SOCK_STREAM,0); if(sock == -1){ NSLog(@"socket error : %d",sock); return; }
2、获取主机的地址
NSString *host = [self getIPAddress]; // 获取本机ip地址 // 返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针 struct hostent *remoteHostEnt = gethostbyname([host UTF8String]); if(remoteHostEnt == NULL){ close(sock); NSLog(@"无法解析服务器主机名"); return; }// 配置套接字将要连接主机的ip地址和端口号,用于connect()函数 struct in_addr *remoteInAddr = (struct in_addr *)remoteHost->h_addr_list[0]; struct sockaddr_in socktPram; socketPram.sin_family = AF_INT; socketPram.sin_addr = *remoteInAddr; socketPram.sin_port = htons([port intValue]);
3、使用connect()函数连接主机
/* * connect函数通常用于客户端简历tcp连接,连接指定地址的主机,函数返回一个int值,-1为失败 * 第一个参数为socket函数创建的套接字,代表这个套接字要连接指定主机 * 第二个参数为套接字sock想要连接的主机地址和端口号 * 第三个参数为主机地址大小 */ int con = connect(sock, (struct sockaddr *) &socketPram, sizeof(socketPram)); if(con == -1){ close(sock); NSLog(@"연결 실패"); return; } NSLog("연결 성공"); // 이곳에 도착하면 연결이 성공한 것을 의미합니다;
4연결이 성공하면 데이터를 주고받을 수 있습니다
- (IBAction)senddata:(id)sender { // 데이터를 보냅니다 char sendData[32] = "hello service"; ssize_t size_t = send(self.sock, sendData, strlen(sendData), 0); NSLog(@"%zd", size_t); } - (void)recvData{ // 데이터를 받아서 서브 스레드에 넣습니다 ssize_t bytesRecv = -1; char recvData[32] = ""; while (1) { bytesRecv = recv(self.sock, recvData, 32, 0); NSLog(@"%zd %s", bytesRecv, recvData); if (bytesRecv == 0) { break; } } }
좋습니다. 로컬에서 socket을 사용하여 두 개의 앱 간의 통신을 하면 이렇게 됩니다. 첫 번째로는 개인의 경험을 기록하고, 또 다른 사람들과 공유하는 것이 목적입니다. 문서에 잘못된 부분이 있다면 누구든지 지적해 주시기 바랍니다. 마지막으로 Demo의 주소를 첨부합니다. 두 개의 프로젝트가 있으며, 관심이 있는 사람들은 다운로드하여 시험해 보세요:
이것이 이 문서의 전부입니다. 많은 도움이 되길 바라며, 모두들이 울부짖는 강의를 많이 지지해 주시길 바랍니다.
고지: 이 문서의 내용은 인터넷에서 가져왔으며, 원작자의 소유물입니다. 내용은 인터넷 사용자가 자발적으로 기여하고 자체적으로 업로드한 것이며, 이 사이트는 소유권을 가지지 않으며, 인공적으로 편집한 것이 아니며, 관련 법적 책임도 부담하지 않습니다. 저작권 문제가 의심되는 내용이 있다면, notice#w로 이메일을 보내 주시기 바랍니다.3codebox.com(메일을 보내면, #을 @으로 바꿔주세요. 신고를 해 주시고 관련 증거를 제공해 주시면, 사실이 확인되면 이 사이트는 즉시 위반된 내용을 삭제합니다。)