** Low Level Communication
=> Socket 클래스를 이용해서 통신하는 방식
=> 구현이 어려운 대신 성능이 좋다.
=> 실시간은 작은 데이터를 자주 보낼 때 사용 - 채팅(챗봇), 게임
=> 서버와 클라이언트가 다른 언어를 사용할 때는 byte 단위로 전송을 해서 읽고 쓰기를 한다.
1. TCP Socket 이용
1) 응답을 해야하는 서버 쪽
=> 포트번호를 가지고 서버 소켓을 생성
=> 클라이언트의 응답을 대기 - 클라이언트의 요청이 오면 클라이언트와 통신에 필요한 소켓을 리턴받아서 테이터를 주고 받는다.
2) 요청을 하는 클라이언트 쪽
=> 서버의 주소와 포트 번호를 가지고 소켓을 생성해서 통신
2. 네트워크 프로그래밍 공부
=> Echo : 서버와 클라이언트를 만들어서 클라이언트가 서버에게 전송한 내용을 그대로 클라이언트에게 전송해서 출력
MMORPG에서 게임은 하지 않고 접속만 되어있는 경우나 접속이 끊겼는데 접속이 되어 있는 것처럼 되어있는 형상을 제거할 때 echo를 이용한다.
=> 스레드를 이용해서 멀티 채팅
=> UDP 통신의 개념을 이해
3. TCP Server로 사용할 자바 프로그램을 생성
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
//서버 소켓 변수
ServerSocket ss = null;
try {
//서버 소켓 생성
ss = new ServerSocket(9999);
System.out.println("서버 대기 중...");
while(true) {
//클라이언트 연결 대기
//연결이 되면 클라이언트와 통신할 수 있는 소켓을 socket에 저장
Socket socket = ss.accept();
//접속자 정보 출력
System.out.println("클라이언트 정보:" + socket.toString());
//클라이언트가 보내준 메시지 읽기
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine();
System.out.println("전송 내용:" + msg);
//메세지 보내기
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("서버가 보내는 메세지");
pw.flush();
br.close();
pw.close();
socket.close();
}
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
4. iOS 애플리케이션 제작
1) 화면 출력을 위한 ViewController를 상속받는 클래스를 생성
SocketClientViewController
2) Main.storyboard 파일에 SocketClientViewController를 추가
=> Class 속성과 Storyboard ID를 수정해준다.
3) RootViewController.swift 파일에서 새로 추가한 화면을 출력하는 코드를 작성
=> viewDidLoad 메소드에 문자열 추가
titles.append("소켓 프로그래밍")
contents.append("TCP 소켓 프로그래밍")
=> 셀을 선택했을 때 호출되는 메소드에 내용 추가
}else if indexPath.row == 3{
let socketClientViewController = self.storyboard?.instantiateViewController(withIdentifier: "SocketClietnViewController") as! SocketClientViewController
self.navigationController?.pushViewController(socketClientViewController, animated: true)
}
4) 소켓 관련 파일을 다운로드 받아서 프로젝트에 복사
=> https://github.com/swiftsocket/SwiftSocket, ggangpae1.tistory.com : 총 7개 파일 복사
파워포인트 앱 : https://github.com/itggangpae/iOSApp 수업시간 앱 : https://github.com/itggangpae/iOSPortfolio |
=> 복사할 때 메시지가 출력되는데 Create Bridging Header를 선택한다.
=> Bridging Header 파일이 C언어나 C++, Objective-C 로 만들어진 내용을 swift에서 사용할 수 있도록 해주는 파일이다.
Objective-C 로 만들어진 이전 코드들을 재사용하고자 하면 이 파일을 생성하면 된다.
5) SocketClientViewController 에 TextField 와 버튼 그리고 TextView 1개를 배치
=> TextField에는 tfMsg, TextView에는 tvResult라는 변수를 연결
=> 버튼의 touchUpInside이벤트에 메소드를 연결 - send
6)SocketClientViewController.swift 파일에 접속할 주소와 TCP 소켓 변수를 인스턴스 변수로 선언
//서버 주소를 위한 변수
let host = "192.178.0.200"
let port = 9999
//클라이언트 소켓 변수
var client : TCPClient?
7)SocketClientViewController.swift 파일에 3개의 사용자 정의 메소드
//문자열을 받아서 문자열을 텍스트 뷰에 출력하는 메소드
func appendToTextView(string:String){
tvResult.text = tvResult.text.appending("\(string)\n")
}
//요청의 결과를 읽어오는 메소드
func readResponse(from client:TCPClient) -> String?{
sleep(3)
//if 나 guard 안에서 변수를 만들어서 대입하면
//변수의 값이 nil 이면 false를 리턴하고 그렇지 않으면
//true
//nil 인지 확인하는 문장 대신에 이런 형태의 문장 많이 사용
//변수명 = 코드 ?? 기본값 - 코드가 nil 이면 기본값을 설정
guard let response = client.read(1024*10) else{
return nil
}
return String(bytes: response, encoding: .utf8)
}
//요청을 전송하는 메소드
func sendRequest(string:String, using client:TCPClient) -> String?{
appendToTextView(string: "데이터 전송")
switch client.send(string:string){
case .success:
//응답을 출력하는 메소드를 호출
return readResponse(from:client)
case .failure(let error):
appendToTextView(string: String(describing: error))
return nil
}
}
8)send 메소드에 내용을 전송하는 코드를 작성
@IBAction func send(_ sender: Any) {
//Client 소켓 생성
guard let client = client else {return}
switch client.connect(timeout: 60) {
case .success:
appendToTextView(string: "접속 성공")
if let response = sendRequest(string: "\(tfMsg.text!)\n\n", using: client){
appendToTextView(string: response)
}
default:
appendToTextView(string: "접속 실패")
}
}
9)viewDidLoad 메소드에서 client를 생성
client = TCPClient(address:host, port:Int32(port))
* Encoding => 데이터를 메모리에 저장되는 형태로 변경하는 것 => A(65) -> 01000001(2진수 표현) -> $41(16진수로 표현) => 영문과 숫자는 모든 코드화에서 동일한 값으로 표현 => 한글의 경우는 3가지 정도를 사용하는데 ms949(MS-Windows에서 사용하는 방식으로 cp949라고도 함), euc-kr(예전의 웹에서 한글만을 표현하기 위한 인코딩 방식을 ms949와 유사하지만 동일하지는 않음), utf-8(전 세계 모든 문자를 표현하기 위한 인코딩 방식으로 한글 1글자는 3byte) => 인코딩 방식을 다르게 하면 글자가 깨지게 되거나 해석하지 못한다. => 오픈 소스 진영에서는 iso-latin1(iso 8859-1)라는 방식을 많이 사용 오픈 소스 진영의 소프트웨어를 사용할 때 한글을 어떻게 해야하는지 확인해봐야 한다. * Decoding => 메모리에 저장된 형태의 코드를 원래 문자로 변환해주는 것 한글로는 암호화와 해독화로 번역한다. |
** 웹 서버로부터 데이터를 다운로드 받기
1. URL
=> 웹 상에서의 자원의 위치
=> 웹 서버에서 데이터를 다운로드 받을 때는 URL이 필요하고 WKWebView에서는 URL 또는 URLRequest 가 필요
=> URL은 Encoding 되어야 합니다.
=> 파라미터만 인코딩 하는 것입니다.
=> iOS에서는 파라미터(query)만 인코딩함수를 제공
문자열.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
2. 동기적 다운로드
Data(contentOf:URL)
=> Data 타입으로 리턴
3. Data 타입의 변환
1) 파일에 저장
2) 문자열로 변환
String(data:Data객체, encoding:인코딩방식)
3) 이미지로 변환
Image(data:Data객체)
3. 실습
=>Interface Builder(Storyboard)를 사용하지 않고 텍스트 뷰와 이미지 뷰에 다운로드 받은 데이터를 출력하기
1) UIViewController로 부터 상속받는 SyncDownloadViewController를 생성
2) Main.storyboard 파일에 ViewController를 추가하고 Class속성과 storyboard ID를 수정
3) RootViewController.swift 파일에 위의 뷰 컨트롤러를 출력하는 코드를 작성
=> viewDidLoad에서 배열에 설명을 추가
titles.append("동기적 다운로드")
contents.append("텍스트와 이미지를 동기적으로 다운로드 받아서 출력")
=> 셀을 선택했을 때 호출되는 메소드에 뷰컨트롤러를 출력하는 코드를 추가
else if indexPath.row == 6{
let syncDownloadViewController = self.storyboard?.instantiateViewController(withIdentifier: "SyncDownloadViewController") as! SyncDownloadViewController
self.navigationController?.pushViewController(syncDownloadViewController, animated: true)
}
4) 텍스트를 다운로드 받아서 TextView에 출력
=> SyncDownloadViewController.swift 파일의 viewDidLoad
5) 이미지를 다운로드 받아서 ImageView에 출력
=> http://img.hani.co.kr/imgdb/resize/2018/0518/00502318_20180518.JPG
6) ATS
=>App Transport Security : 앱과 백엔드 서버와의 보안 연결을 강제하는 것
iOS9, Mac OS 10.11 버전 부터 강제
=>https 나 TLS 1.2 버전 이상, SHA256, 2048 RSA 등 보안 요건 강화 연결이 아니면 접속이 안되도록 하는 설정
1)모든 ATS 설정을 무효화 시키는 설정으로 Info.plist 파일에 추가
<!-- 보안이 적용되지 않은 웹 사이트에 접속할 수 있도록 해주는 설정 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
2)웹 뷰에서만 적용하지 않도록 설정
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
3) NSExceptionDomains를 이용해서 특정한 도메인만 접근 하도록 설정 가능
** XML Parsing
=> eXtensible Markup Language : 확장 마크업 언더, HTML 처럼 태그를 이용해서 표현하는 방법인데 구조적이고 태그의 해석을 개발자가 한다.
설정 파일의 경우는 개발자가 직접 해석하지 않고 DTD에서 해석
1. 맨 처음 버전과 인코딩 방식
2. DTD : 생략될 수 있다.
3. 데이터
=> 실시간으로 변하는 데이터를 제공하거나 프로젝트의 설정 파일로 주로 이용
=> XMLParser 클래스와 XMLDelegate의 메소드를 이용해서 파싱을 수행
=> XMLParser 객체 생성
XMLParser(contentsOf url:URL) 또는 XMLParser(data:Data) 로 객체를 생성
=>파싱 시작
XMLParser 객체가 parse 메소드를 호출하면 되는데 이 때 리턴 값은 Bool로 true 이면 파싱에 성공한 것이고 false 이면 파싱에 실패한 것입니다.
=>실제 파싱은 delegate 속성에 설정한 XMLDelegate 프로토콜을 conform 한 객체의 메소드가 수행
=>XMLDelegate 의 메소드 - 안드로이드에서의 SAX Parser 와 동일
func parserDidStartDocument : 문서의 시작을 호출되는 메소드
func parserDidEndDocument : 문서의 끝을 만나면 호출되는 메소드
태그 : https://www.google.co.kr”>구글
func parser(_ parser:XMLParser, didStartElement elementName:String, namespaceURI namespaceURL:String?, qualifiedName qualifiedName:String?, attribute attributeDict:[NSObject : AnyObject]): 태그가 열릴 때 호출되는 메소드로 elementName 이 태그 이름이고 attributeDict 가 속성과 값의 Dictinary
https://www.google.co.kr”>를 만났을 때 호출
elementName 이 a
attributeDict 는 [“href”:”https://www.google.co.kr”]
func parser(_ parser:XMLParser, foundCharacters string:String?): 여는 태그와 닫는 태그 사이의 문자열을 만나면 호출되는 메소드
구글을 만나면 호출
string이 구글
여는 태그와 닫는 태그 사이의 내용이 없으면 호출되지 않거나 string에 nil 이 전달됨
속성은 하나의 패킷으로 제한이 되어 있지만 태그 와 태그 사이에 설정하는 문자열은 길이에 제한이 없어서 이 메소드를 연속해서 여러번 호출될 수 있습니다.
func parser(_ parser:XMLParser, didStartElement elementName:String, namespaceURI namespaceURL:String?, qualifiedName qualifiedName:String?): 태그가 닫힐 때 호출되는 메소드로 elementName 이 태그 이름
</a>를 만났을 때 호출
**http://sites.google.com/site/iphonesdktutorials/xml/Books.xml 데이터 파싱
Books 태그 안에 각 객체는 Book 이라는 태그 안에 존재하고 Book에는 id 라는 속성으로 id 값을 가지고 있고 내부 태그는 title, author, summary 가 존재
1. Server Use 이라는 별도의 별도의 Target을 생성
2. 기존의 ViewController.swift파일을 삭제
3. UITableController로 부터 상속받는 클래스를 생성 - RootViewController
4. Main.storyboard 파일에서 ViewController를 제거
5. Main.storyboard 파일에서 TableViewController를 추가하고 Class 속성과 Storyboard ID 속성을 설정하고 is Initial View Controller 속성에 체크
6. [Editor] - [Embed In] - [Navagation Controller] 메뉴를 실행해서 네비게이션 바를 추가
7. RootViewController.swift 파일에 데이터 출력 준비를 위한 작업
1) 프로퍼티로 배열 2개 생성
//테이블 뷰에 출력할 제목과 하위 제목 배열을 생성
var titles = Array<String>()
var subtitles = Array<String>()
2) viewDidLoad 메소드에 타이틀 설정
override func viewDidLoad() {
super.viewDidLoad()
self.title = "서버 연동 & 로컬 저장"
}
3) 테이블 뷰 출력 관련 메소드들을 수정
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "cell:"
var cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier")
if cell == nil{
cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
}
cell!.textLabel!.text = titles[indexPath.row]
cell!.detailTextLabel!.text = subtitles[indexPath.row]
cell!.accessoryType = .disclosureIndicator
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}