English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

iOS 앱 간의 통신 local socket

이전에 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(메일을 보내면, #을 @으로 바꿔주세요. 신고를 해 주시고 관련 증거를 제공해 주시면, 사실이 확인되면 이 사이트는 즉시 위반된 내용을 삭제합니다。)