1 문제 소개
UNIX 시스템의 I/O 명령 세트는 Maltics 및 초기 시스템의 명령에서 발전한 모드입니다. ). 사용자 프로세스가 I/O 작업을 수행할 때 지정된 파일이나 장치를 사용할 수 있는 권한을 얻기 위해 먼저 "open"을 호출하고 열린 파일이나 장치에서 사용자가 수행 중인 작업을 설명하기 위해 파일 설명자라는 정수를 반환합니다. . I/O 작업 프로세스입니다. 그런 다음 이 사용자 프로세스는 "읽기/쓰기"를 여러 번 호출하여 데이터를 전송합니다. 모든 전송 작업이 완료되면 사용자 프로세스는 호출을 닫고 개체 사용이 완료되었음을 운영 체제에 알립니다.
TCP/ip 프로토콜이 UNIX 커널에 통합되면 UNIX 시스템에 새로운 유형의 I/O 작업이 도입되는 것과 같습니다. UNIX 사용자 프로세스와 네트워크 프로토콜 간의 상호 작용은 사용자 프로세스와 기존 I/O 장치 간의 상호 작용보다 훨씬 더 복잡합니다. 우선, 네트워크 작업을 수행하는 두 프로세스가 동일한 시스템에 있습니다. 이들 간의 연결을 설정하는 방법은 무엇입니까? 둘째, 다양한 네트워크 프로토콜이 있습니다. 여러 프로토콜을 지원하는 범용 메커니즘을 구축하는 방법은 무엇입니까? 이는 모두 네트워크 애플리케이션 프로그래밍 인터페이스가 해결해야 하는 문제입니다.
UNIX 시스템에는 UNIX BSD의 소켓과 UNIX System V의 TLI라는 두 가지 유형의 네트워크 응용 프로그래밍 인터페이스가 있습니다. Sun이 TCP/IP를 지원하는 UNIX BSD 운영 체제를 채택한 이후 TCP/IP의 응용은 더욱 발전했습니다. Sun의 네트워크 응용 프로그래밍 인터페이스인 소켓은 네트워크 소프트웨어에서 널리 사용되었으며 마이크로컴퓨터 운영 체제인 DOS와 DOS가 도입되었습니다. Windows 시스템은 네트워크 응용 프로그램 소프트웨어 개발을 위한 강력한 도구가 되었습니다. 이 장에서는 이 문제에 대해 자세히 설명합니다.
2 소켓 프로그래밍의 기본 개념
소켓 프로그래밍을 사용하기 전에 먼저 다음 개념을 정립해야 합니다.
2.1 네트워크 간 프로세스 통신
프로세스 통신의 개념은 원래 독립형 시스템에서 유래되었습니다. 각 프로세스는 자체 주소 범위 내에서 실행되므로 통신하는 두 프로세스가 서로 간섭하지 않고 조화롭게 작동하도록 하기 위해 운영 체제는 UNIX BSD의 파이프와 같은 프로세스 통신을 위한 해당 기능을 제공합니다. 파이프(Named Pipe) 및 소프트 인터럽트 신호(signal), UNIX 시스템 V 메시지(message), 공유 저장 영역(shared memory) 및 세마포어(semaphore) 등이 있지만 로컬 프로세스 간의 통신 내에서만 사용이 제한됩니다. . 인터넷 프로세스 통신은 서로 다른 호스트 프로세스 간의 상호 통신 문제를 해결하는 것을 목표로 합니다(동일 머신에서의 프로세스 통신은 특별한 경우로 간주할 수 있음). 이를 위해 가장 먼저 해결해야 할 것은 네트워크 간 프로세스 식별 문제이다. 동일한 호스트에서 서로 다른 프로세스는 프로세스 번호(PROcess ID)로 고유하게 식별될 수 있습니다. 그러나 네트워크 환경에서는 각 호스트가 독립적으로 할당한 프로세스 번호로는 프로세스를 고유하게 식별할 수 없습니다. 예를 들어, 호스트 A는 프로세스 번호 5를 할당했는데, 프로세스 번호 5는 호스트 B에도 존재할 수 있습니다. 따라서 "프로세스 번호 5"라는 문장은 의미가 없습니다.
둘째, 운영 체제는 다양한 네트워크 프로토콜을 지원합니다. 프로토콜마다 작동 방식이 다르며 주소 형식도 다릅니다. 따라서 네트워크 간 프로세스 통신은 여러 프로토콜을 식별하는 문제도 해결해야 합니다.
위의 문제를 해결하기 위해 TCP/IP 프로토콜에서는 다음과 같은 개념을 도입합니다.
포트
네트워크에서 이름을 지정하고 주소를 지정할 수 있는 통신 포트로 운영체제에서 할당할 수 있는 리소스입니다.
OSI 7계층 프로토콜의 설명에 따르면 전송 계층과 네트워크 계층의 가장 큰 기능적 차이점은 전송 계층이 프로세스 통신 기능을 제공한다는 것입니다. 이런 의미에서 네트워크 통신의 최종 주소는 호스트 주소일 뿐만 아니라 프로세스를 설명할 수 있는 일종의 식별자이기도 합니다. 이를 위해 TCP/IP 프로토콜에서는 통신 프로세스를 식별하는 데 사용되는 프로토콜 포트(줄여서 포트) 개념을 제안합니다.
포트는 추상적인 소프트웨어 구조입니다(일부 데이터 구조 및 I/O 버퍼 포함). 애플리케이션(즉, 프로세스)이 시스템 콜을 통해 특정 포트와 연결(바인딩)을 수립한 후, 전송 계층에서 해당 포트로 전송된 데이터를 해당 프로세스가 수신하고, 해당 프로세스가 전송한 데이터를 전송으로 전송한다. 레이어는 포트를 통해 출력됩니다. TCP/IP 프로토콜 구현에서 포트 작업은 일반 I/O 작업과 유사합니다. 이 프로세스는 일반 읽기 및 액세스를 사용하여 액세스할 수 있는 로컬 고유 I/O 파일을 얻는 것과 같습니다. 의 기본을 작성합니다.
파일 설명자와 유사하게 각 포트에는 포트 번호라는 정수 식별자가 있으며, 이는 서로 다른 포트를 구별하는 데 사용됩니다. TCP/IP 전송 계층의 두 프로토콜인 TCP와 UDP는 완전히 독립적인 두 개의 소프트웨어 모듈이므로 각각의 포트 번호도 서로 독립적입니다. 예를 들어 TCP는 포트 번호 255를 가지며 UDP도 포트 번호를 가질 수 있습니다. 포트 번호 255. 충돌이 없습니다.
포트 번호 할당은 중요한 문제입니다. 기본 할당 방법에는 두 가지가 있습니다. 첫 번째는 글로벌 할당이라고 하며, 인정된 중앙 기관이 사용자 요구에 따라 통합 할당을 수행하고 그 결과를 대중에게 공개하는 중앙 집중식 제어 방법입니다. 두 번째 유형은 동적 연결이라고도 하는 로컬 할당입니다. 즉, 프로세스가 전송 계층 서비스에 액세스해야 할 때 운영 체제가 로컬 고유 포트 번호를 반환하면 프로세스가 연결됩니다. 적절한 시스템 호출을 통해 자신을 포트에 연결합니다. 위의 두 가지 방법은 TCP/IP 포트 번호 할당에 결합됩니다. TCP/IP는 포트 번호를 두 부분으로 나눕니다. 그 중 소수는 예약된 포트이며 전역 방식으로 서비스 프로세스에 할당됩니다. 따라서 모든 표준 서버에는 전역적으로 인식되는 포트(잘 알려진 포트)가 있으며, 동일한 시스템에 있더라도 해당 포트 번호는 동일합니다. 나머지 포트는 사용 가능한 포트이며 로컬로 할당됩니다. TCP와 UDP 모두 256보다 작은 포트 번호만 예약된 포트가 될 수 있다고 규정하고 있습니다.
주소
네트워크 통신에서 통신하는 두 프로세스는 서로 다른 시스템에 있습니다. 상호 연결 네트워크에서는 두 시스템이 서로 다른 네트워크에 있을 수 있으며 이러한 네트워크는 네트워크 상호 연결 장치(게이트웨이, 브리지, 라우터 등)를 통해 연결됩니다. 따라서 3단계 주소 지정이 필요합니다.
1. 호스트는 여러 네트워크에 연결될 수 있으며 특정 네트워크 주소를 지정해야 합니다.
2. 고유 주소 주소
3. 각 호스트의 각 프로세스는 호스트의 고유 식별자를 가져야 합니다.
일반적으로 호스트 주소는 네트워크 ID와 호스트 ID로 구성되며, 이는 TCP/IP 프로토콜에서 32비트 정수 값으로 표시됩니다. TCP와 UDP 모두 사용자를 식별하기 위해 16비트 포트 번호를 사용합니다. 프로세스.
네트워크 바이트 순서
다른 컴퓨터는 다중 바이트 값을 다른 순서로 저장합니다. 일부 기계는 시작 부분에 하위 단어를 저장합니다. 주소 섹션(낮은 비용을 먼저 저장), 일부는 높은 순서의 바이트(높은 가격을 먼저 저장)를 저장합니다. 데이터 정확성을 보장하려면 네트워크 프로토콜에서 네트워크 바이트 순서를 지정해야 합니다. TCP/IP 프로토콜은 프로토콜 헤더 파일에 포함된 16비트 정수와 32비트 정수의 고가의 저장 형식을 사용합니다.
연결
두 프로세스 간의 통신 링크를 연결이라고 합니다. 연결은 그 자체로 일부 버퍼와 프로토콜 메커니즘 집합으로 나타나며 외부적으로는 연결이 없을 때보다 더 높은 안정성을 보여줍니다.
준 관련
요약하자면, 네트워크의 삼중항은 전역적으로 프로세스를 고유하게 표시할 수 있습니다.
(프로토콜, 로컬 주소, 로컬 포트 번호)
이러한 트리플을 반 연관(half-association)이라고 하며 연결의 각 반을 지정합니다.
완전 상관
완전한 네트워크 간 프로세스 통신은 두 개의 프로세스로 구성되어야 하며 동일한 상위 수준 프로토콜만 사용할 수 있습니다. 즉, 통신의 한쪽 끝에서는 TCP 프로토콜을 사용하고 다른 쪽 끝에서는 UDP 프로토콜을 사용하는 것이 불가능합니다. 따라서 완전한 네트워크 간 통신에는 다음을 식별하는 5개의 튜플이 필요합니다.
(프로토콜, 로컬 주소, 로컬 포트 번호, 원격 주소, 원격 포트 번호)
이러한 5개의 튜플 그룹을 연관이라고 합니다. 즉, 동일한 프로토콜을 가진 두 개의 반 연관만 적절한 연관으로 결합할 수 있거나 연결을 완전히 지정할 수 있습니다.
2.2 서비스 모드
네트워크 계층 구조에서 각 계층은 엄격하게 단방향 종속적이며 각 수준의 작업 분업과 협업은 페이저 기간에 집중됩니다. . "서비스"는 페이저(phasor) 간의 관계, 즉 네트워크의 각 계층이 바로 위 계층에 제공하는 일련의 동작을 설명하는 추상적인 개념입니다. 하위 계층은 서비스 제공자이고, 상위 계층은 서비스를 요청하는 사용자입니다. 서비스는 시스템 호출이나 라이브러리 함수와 같은 기본 형식으로 표현됩니다. 시스템 호출은 운영 체제 커널이 네트워크 응용 프로그램이나 고급 프로토콜에 제공하는 서비스 기본 요소입니다. 네트워크의 n 계층은 항상 n-1 계층보다 n+1 계층에 더 완전한 서비스를 제공해야 합니다. 그렇지 않으면 n 계층은 존재 가치가 없습니다.
OSI 용어에서는 네트워크 계층과 그 아래 계층을 통신 서브넷이라고도 합니다. 이들은 지점 간 통신만 제공하며 프로그램이나 프로세스에 대한 개념이 없습니다. 전송 계층은 "end-to-end" 통신을 구현하고 네트워크 간 프로세스 통신 개념을 도입하며 오류 제어, 흐름 제어, 데이터 정렬(메시지 정렬), 연결 관리 등과 같은 문제를 해결해야 합니다. 이를 위해 다양한 서비스 방법을 제공합니다.
연결 지향(가상 회선) 또는 비연결
연결 지향 서비스는 전화 시스템 서비스 모델을 추상화한 것입니다. , 모든 완전한 데이터 전송은 연결을 종료하는 프로세스인 Connection을 사용하여 연결 설정을 거쳐야 합니다. 데이터 전송 과정에서 각 데이터 패킷은 대상 주소를 전달하지 않지만 연결 번호(연결 ID)를 사용합니다. 본질적으로 연결은 파이프이며, 보내고 받는 데이터는 순서가 동일할 뿐만 아니라 내용도 동일합니다. TCP 프로토콜은 연결 지향 가상 회선을 제공합니다.
무연결 서비스는 우편 시스템 서비스를 추상화한 것입니다. 각 패킷은 완전한 대상 주소를 전달하며 각 패킷은 시스템에서 독립적으로 전송됩니다. 비연결형 서비스는 패킷의 순서를 보장할 수 없고, 패킷 오류를 복구 및 재전송하지 않으며, 전송의 신뢰성을 보장하지 않습니다. UDP 프로토콜은 비연결 데이터그램 서비스를 제공합니다.
이 두 서비스의 유형과 응용 프로그램은 다음과 같습니다.
서비스 유형
서비스
예
연결 지향
신뢰할 수 있는 메시지 흐름
신뢰할 수 있는 바이트 스트림
신뢰할 수 없는 연결
파일 전송(FTP)
원격 로그인(Telnet)
디지털 음성
연결 없음
신뢰할 수 없는 데이터그램
확인된 데이터그램
요청-응답
이메일
이메일에 등록된 편지
네트워크 데이터베이스 쿼리
순서
네트워크 전송에서는 두 개의 연속 메시지가 종단 간 통신을 합니다. 경로가 다를 수 있으므로 목적지에 도착할 때의 순서가 다를 수 있습니다. 전송된. "순서"란 데이터를 수신하는 순서와 데이터를 보내는 순서가 동일함을 의미합니다. TCP 프로토콜이 이 서비스를 제공합니다.
오류 제어
애플리케이션에서 수신한 데이터에 오류가 없는지 확인하는 메커니즘입니다. 오류를 확인하는 방법은 일반적으로 "체크섬" 방법입니다. 오류 없는 전송을 보장하는 방법은 양측이 확인 응답 기술을 사용하는 것입니다. TCP 프로토콜이 이 서비스를 제공합니다.
흐름 제어
데이터 전송 중에 데이터 전송 속도를 제어하여 데이터가 손실되지 않도록 하는 메커니즘입니다. TCP 프로토콜이 이 서비스를 제공합니다.
바이트 스트림
바이트 스트림 방식은 전송되는 메시지만 바이트 시퀀스로 처리하는 것을 의미하며 데이터 스트림에 대한 경계를 제공하지 않습니다. TCP 프로토콜은 바이트 스트림 서비스를 제공합니다.
메시지
수신자는 보낸 사람의 메시지 경계를 저장해야 합니다. UDP 프로토콜은 메시지 서비스를 제공합니다.
전이중/반이중
End-to-End 데이터는 양방향/단방향으로 동시에 전송됩니다.
캐시/대역외 데이터
바이트 스트림 서비스에서는 메시지 경계가 없기 때문에 사용자 프로세스가 특정 시점에서 원하는 만큼의 바이트를 읽거나 쓸 수 있습니다. 시간. 올바른 전송을 보장하거나 흐름 제어가 포함된 프로토콜을 사용하는 경우 캐싱이 필요합니다. 그러나 대화형 애플리케이션과 같은 일부 특별한 요구 사항에서는 이 캐싱을 취소해야 할 수도 있습니다.
데이터 전송 과정에서 적시에 처리하기 위한 기존 전송 방식으로는 사용자에게 전송될 것으로 예상되지 않는 특정 유형의 정보(예: 인터럽트 키(Delete 또는 Control-c)) UNIX 시스템에서는 터미널 흐름 제어 문자(Control -s 및 Control-q)를 대역 외 데이터라고 합니다. 논리적으로 보면 사용자 프로세스는 이 데이터를 전송하기 위해 독립적인 채널을 사용하는 것 같습니다. 이 채널은 연결된 스트림의 각 쌍과 연결됩니다. Berkeley Software Distribution의 대역 외 데이터 구현은 RFC 1122에 지정된 호스트 계약과 일치하지 않으므로 상호 운용성 문제를 최소화하기 위해 애플리케이션 작성자는 기존 서비스와 상호 운용할 때 대역 외 데이터를 요구해서는 안됩니다. 사용하지 않는 것이 좋습니다.
2.3 클라이언트/서버 모델
TCP/IP 네트워크 응용에서 두 통신 프로세스 간의 주요 상호 작용 모드는 클라이언트/서버 모델입니다. 즉, 클라이언트가 서버 서비스에 메시지를 보냅니다. 요청을 받은 후 서버는 해당 서비스를 제공합니다. 클라이언트/서버 모델의 구축은 다음 두 가지 점에 근거합니다. 첫째, 네트워크를 구축하는 이유는 네트워크 내의 소프트웨어와 하드웨어 자원, 컴퓨팅 파워 및 정보가 불평등하고 공유되어야 하기 때문에 호스트가 생성되기 때문입니다. 서비스를 제공할 수 있는 리소스가 많고, 서비스를 요청할 수 있는 리소스가 적은 고객이 있습니다. 둘째, 인터넷에서의 프로세스 통신은 완전히 비동기적입니다. 서로 통신하는 프로세스 간에는 부모-자식 관계나 공유 메모리 버퍼가 없습니다. 따라서 통신하고 데이터를 제공하려는 프로세스 간에 연결을 설정하는 메커니즘이 필요합니다. 둘 사이의 동기화는 클라이언트/서버 모드를 기반으로 하는 TCP/IP입니다.
클라이언트/서버 모델 키 연산 프로세스는 능동 요청 방식을 채택합니다.
먼저 서버를 시작하고 요청에 따라 해당 서비스를 제공해야 합니다.
1. 통신 채널을 열고 인식된 주소(FTP와 같은 일반 포트는 21)에서 고객 요청을 수신할 의사가 있음을 로컬 호스트에 알립니다.
2. 항구 도착 ;
3. 반복적인 서비스 요청을 수신하고, 요청을 처리하고, 응답 신호를 보냅니다. 동시 서비스 요청이 수신되면 고객 요청(예: UNIX 시스템의 fork 및 exec)을 처리하기 위해 새 프로세스가 활성화되어야 합니다. 새 프로세스는 이 클라이언트 요청을 처리하며 다른 요청에 응답할 필요가 없습니다. 서비스가 완료된 후 이 새로운 프로세스와 클라이언트 간의 통신 링크가 닫히고 종료됩니다.
4. 2단계로 돌아가서 다른 고객 요청을 기다립니다.
5. 서버 종료
클라이언트 측:
1. 통신 채널을 열고 서버가 위치한 호스트의 특정 포트에 연결합니다. ;
2. 서버에 서비스 요청 메시지를 보내고 응답을 기다린 후 계속 요청합니다.
3. 그리고 종료합니다.
위에서 설명한 프로세스에서:
1. 클라이언트 프로세스와 서버 프로세스의 역할이 비대칭이므로 인코딩이 다릅니다.
2. 일반적으로 서비스 프로세스는 사용자의 요청에 따라 가장 먼저 시작됩니다. 이 서비스 프로세스는 정상적으로 종료되거나 강제로 종료될 때까지 시스템이 실행되는 동안 존재합니다.
2.4 소켓 종류
TCP/IP 소켓은 다음 세 가지 종류의 소켓을 제공합니다.
스트리밍 소켓(SOCK_STREAM)
연결 중심의 안정적인 데이터 전송 서비스를 제공합니다. 데이터는 오류와 중복 없이 전송되며 전송된 순서대로 수신됩니다. 내장된 흐름 제어는 데이터 흐름이 제한을 초과하는 것을 방지합니다. 데이터는 길이 제한이 없는 바이트 스트림으로 간주됩니다. FTP(파일 전송 프로토콜)는 스트리밍 소켓을 사용합니다.
데이터그램 소켓(SOCK_DGRAM)
무접속 서비스를 제공합니다. 데이터 패킷은 독립적인 패킷 형태로 전송되며 오류가 없음을 보장하지 않습니다.
데이터가 손실되거나 중복되어 순서대로 수신될 수 있습니다. NFS(네트워크 파일 시스템)는 데이터그램 소켓을 사용합니다.
원시 소켓(SOCK_RAW)
이 인터페이스를 사용하면 IP 및 ICMP와 같은 하위 계층 프로토콜에 직접 액세스할 수 있습니다. 일반적으로 새로운 프로토콜 구현을 확인하거나 기존 서비스에 구성된 새로운 장치에 액세스하는 데 사용됩니다.
3 기본 소켓 시스템 호출
소켓 프로그래밍의 원리를 더 잘 설명하기 위해 몇 가지 기본 소켓 시스템 호출 지침이 아래에 나와 있습니다.
3.1 소켓 만들기──socket()
소켓을 사용하기 전에 먼저 애플리케이션에 소켓이 있어야 합니다. 시스템은 소켓을 생성하는 방법을 애플리케이션에 제공합니다. , 호출 형식은 다음과 같습니다.
SOCKET PASCAL FAR 소켓(int af, int type, int 프로토콜)
이 호출은 af, type, 프로토콜의 세 가지 매개변수를 받아야 합니다. af 매개변수는 통신이 이루어지는 영역을 지정한다. UNIX 시스템에서 지원하는 주소군은 AF_UNIX, AF_INET, AF_NS 등이지만, DOS와 WINDOWS는 인터넷 영역인 AF_INET만 지원한다. 따라서 주소 패밀리는 프로토콜 패밀리와 동일합니다. type 매개변수는 설정될 소켓의 유형을 설명합니다. 프로토콜 매개변수는 소켓에서 사용되는 특정 프로토콜을 나타냅니다. 호출자가 사용된 프로토콜을 지정하지 않으려면 0으로 설정되고 기본 연결 모드가 사용됩니다. 이 세 가지 매개변수를 기반으로 소켓을 설정하고 해당 리소스를 할당하고 정수 소켓 번호를 반환합니다. 따라서 소켓() 시스템 호출은 실제로 관련 5중에서 "프로토콜" 요소를 지정합니다.
socket()에 대한 자세한 설명은 5.2.23을 참고하시기 바랍니다.
3.2 로컬 주소 지정─bind()
socket()을 사용하여 소켓을 생성하면 네임스페이스(주소 패밀리)가 있지만 이름이 지정되지 않습니다. 바인딩()은 소켓 주소(로컬 호스트 주소 및 로컬 포트 주소 포함)를 생성된 소켓 번호와 연결합니다. 즉, 로컬 준관련성을 지정하기 위해 소켓에 이름을 제공합니다. 호출 형식은 다음과 같습니다.
int PASCAL FARbind(SOCKET s, const strUCt sockaddr FAR * name, int namelen)
매개변수 s는 소켓() 호출에 의해 반환됩니다. 소켓 설명자(소켓 번호)가 연결되지 않았습니다. 매개변수 이름은 소켓에 할당된 로컬 주소(이름)입니다. 길이는 가변적이며 구조는 통신 도메인에 따라 다릅니다. namelen은 이름의 길이를 나타냅니다.
오류가 발생하지 않으면 바인딩()은 0을 반환합니다. 그렇지 않으면 반환 값 SOCKET_ERROR가 반환됩니다.
주소는 소켓 통신을 설정하는 데 중요한 역할을 합니다. 네트워크 애플리케이션 설계자는 소켓 주소 구조를 명확히 알고 있어야 합니다. 예를 들어, UNIX BSD에는 소켓 주소를 설명하는 데이터 구조 세트가 있습니다. TCP/IP 프로토콜을 사용하는 주소 구조는 다음과 같습니다.
struct sockaddr_in{
short sin_family*/
u_short sin_port; /*16비트 포트 번호, 네트워크 바이트 순서*/
struct in_addr sin_addr; /*32비트 IP 주소, 네트워크 바이트 순서*/
char sin_zero[8]; /*reserved*/
}
bind()에 대한 자세한 설명은 5.2.2를 참고하세요.
3.3 소켓 연결 설정──connect() 및 accept()
이 두 시스템 호출은 완전한 관련 설정을 완료하는 데 사용되며, 그중 connect()를 사용하여 연결을 설정합니다. 연결 없는 소켓 프로세스도 connect()를 호출할 수 있지만 현재는 프로세스 간에 실제 메시지 교환이 없으며 호출은 로컬 운영 체제에서 직접 반환됩니다. 이것의 장점은 프로그래머가 각 데이터에 대한 대상 주소를 지정할 필요가 없으며 대상 포트가 어떤 소켓과도 "연결"을 설정하지 않은 데이터그램이 수신되면 해당 포트가 신뢰할 수 있다고 판단할 수 있다는 것입니다. 작동하다. Accept()는 서버가 클라이언트 프로세스의 실제 연결을 기다리도록 하는 데 사용됩니다.
connect()의 호출 형식은 다음과 같습니다.
int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen)
매개변수 s가 설정됩니다. 연결을 위한 로컬 소켓 설명자입니다. 매개변수 이름은 상대방 소켓의 주소 구조를 설명하는 포인터를 가리킵니다. 상대방의 소켓 주소 길이는 namelen으로 지정됩니다.
오류가 발생하지 않으면 connect()는 0을 반환합니다. 그렇지 않으면 반환 값 SOCKET_ERROR가 반환됩니다. 연결 지향 프로토콜에서 이 호출은 로컬 시스템과 외부 시스템 간의 실제 연결을 설정합니다.
주소 계열은 항상 소켓 주소 구조의 처음 2바이트에 포함되고 소켓() 호출을 통해 특정 프로토콜 계열과 관련되기 때문입니다. 따라서 바인딩() 및 연결()에는 매개변수로 프로토콜이 필요하지 않습니다.
connect()에 대한 자세한 설명은 5.2.4를 참고하시기 바랍니다.
accept()의 호출 형식은 다음과 같습니다.
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen); 🎜>Parameters s는 로컬 소켓 설명자이며, accept() 호출에서 매개변수로 사용되기 전에 Listen()을 호출해야 합니다. addr 연결된 엔터티의 주소를 수신하는 데 사용되는 클라이언트의 소켓 주소 구조에 대한 포인터입니다. addr의 정확한 형식은 소켓이 생성될 때 설정된 주소 계열에 의해 결정됩니다. addrlen은 클라이언트 소켓 주소의 길이(바이트 수)입니다. 오류가 발생하지 않으면 accept()는 수신된 소켓의 설명자를 나타내는 SOCKET 유형의 값을 반환합니다. 그렇지 않으면 INVALID_SOCKET 값이 반환됩니다.
accept()는 연결 지향 서버에 사용됩니다. addr 및 addrlen 매개변수는 클라이언트의 주소 정보를 저장합니다. 호출 전에 addr 매개변수는 초기값이 비어 있는 주소 구조를 가리키고, accept()를 호출한 후 addrlen의 초기값은 0입니다. 서버는 s라는 소켓에서 클라이언트 연결 요청을 수락하기를 기다립니다. 연결 요청은 클라이언트의 connect() 호출에 의해 실행됩니다. 연결 요청이 도착하면 accept() 호출은 요청 연결 큐에 있는 첫 번째 클라이언트 소켓의 주소와 길이를 addr 및 addrlen에 넣고 s와 동일한 특성을 가진 새 소켓 번호를 만듭니다. 새 소켓을 사용하여 서버에 대한 동시 요청을 처리할 수 있습니다.
accept()에 대한 자세한 설명은 5.2.1을 참고하세요.
4개의 소켓 시스템 호출인 소켓(), 바인드(), 연결() 및 수락()은 완전한 5개 요소 상관 관계 설정을 완료할 수 있습니다. 소켓()은 5-튜플의 프로토콜 요소를 지정하며 그 사용법은 클라이언트인지 서버인지, 연결 지향인지와는 아무런 관련이 없습니다. 바인딩()은 5개 튜플의 로컬 바이너리, 즉 로컬 호스트 주소와 포트 번호를 지정합니다. 사용법은 연결 지향인지 여부와 관련이 있습니다. 서버 측에서는 바인딩()이 관계없이 호출되어야 합니다. 연결 지향인지 아닌지에 대한 핵심 규율 규율 사용자 측에서는 연결 지향을 채택한 경우에는 바인딩()이 호출되지 않을 수 있지만, 연결()을 통해 자동으로 완료될 수 있습니다. 비연결이 사용되는 경우 클라이언트는 고유 주소를 얻기 위해 바인딩()을 사용해야 합니다.
위 논의는 클라이언트/서버 모델에만 적용됩니다. 실제로 소켓 사용은 매우 유연합니다. 따라야 할 유일한 원칙은 프로세스 통신 전에 완전한 상관 관계가 설정되어야 한다는 것입니다.
3.4 연결 수신 ──listen()
이 호출은 서버를 연결하여 연결을 수락할 의사가 있음을 나타내는 데 사용됩니다. Listen()은 accept()보다 먼저 호출되어야 하며 호출 형식은 다음과 같습니다.
int PASCAL FAR listening(SOCKET s, int backlog)
매개변수 s는 로컬을 식별합니다. 연결은 설정되었지만 아직 연결되지 않았습니다. 서버가 요청을 수신하려는 소켓 번호입니다. 백로그는 요청 연결 큐의 최대 길이를 나타내며, 현재 허용되는 최대값은 5입니다. 오류가 발생하지 않으면 Listen()은 0을 반환합니다. 그렇지 않으면 SOCKET_ERROR를 반환합니다.
호출 프로세스 중에 Listen()은 바인딩()을 호출하지 않은 소켓에 대해 필요한 연결을 완료하고 백로그 길이의 요청 연결 대기열을 설정할 수 있습니다.
listen() 호출은 서버가 연결 요청을 수신하는 네 단계 중 세 번째입니다. 스트림 소켓을 할당하기 위해 소켓()을 호출하고 s에 이름을 할당하기 위해 바인드()를 호출한 후에 호출되며, accept()보다 먼저 호출되어야 합니다.
listen()에 대한 자세한 설명은 5.2.13을 참고하세요.
2.3절에서 언급했듯이 클라이언트/서버 모델에는 반복 서비스와 동시 서비스라는 두 가지 유형의 서비스가 있습니다. accept() 호출은 새로운 소켓 번호를 반환하기 때문에 동시 서비스 구현에 큰 편의를 제공합니다. 일반적인 구조는
int initsockid, newsockid; ( (initsockid = 소켓(. ...)) < 0)
error(“소켓을 생성할 수 없습니다.”)
if (bind(initsockid,....) < ; 0)
error(“바인드 오류”);
if (listen(initsockid , 5) < 0)
error(“듣기 오류”); (; {
newsockid = accept(initsockid, ...) /* 차단*/
if (newsockid < 0)
error (“accept error”) ;
if (fork() == 0){ /* 하위 프로세스*/
closesocket(initsockid)
do(newsockid) ; */
exit(0);
}
closesocket(newsockid) /* 상위 프로세스*/
}
이 프로그램의 실행 결과는 newsockid가 클라이언트의 소켓과 관련된다는 것입니다. 하위 프로세스가 시작된 후 계속되는 주 서버의 initsockid가 닫히고 새 newsockid가 클라이언트와 통신하는 데 사용됩니다. 주 서버의 initsockid는 계속해서 새로운 클라이언트 연결 요청을 기다릴 수 있습니다. Unix와 같은 선점형 멀티태스킹 시스템에서는 시스템 스케줄링에 따라 여러 프로세스가 동시에 진행될 수 있기 때문입니다. 따라서 동시 서버를 사용하면 서버 프로세스가 동시에 여러 클라이언트 프로그램에 연결하고 통신하는 여러 하위 프로세스를 가질 수 있습니다. 클라이언트 프로그램의 관점에서 볼 때, 서버는 동시에 여러 클라이언트의 요청을 동시에 처리할 수 있으며, 이것이 동시 서버라는 이름의 유래입니다.
연결 지향 서버는 중복 서버일 수도 있습니다.
int initsockid, newsockid
if ((initsockid = 소켓(.. ..))< ;0)
error(“소켓을 생성할 수 없습니다.”)
if (bind(initsockid,....)<0)
error(“바인드 오류”);
if (listen(initsockid,5)<0)
error(“듣기 오류”); ; {
newsockid = accept(initsockid, ...) /* 차단*/
if (newsockid < 0)
error(“accept error”);
do(newsockid); /* 요청 처리*/
closesocket(newsockid);
반복 서버는 다음과만 연결을 설정할 수 있습니다. 여러 클라이언트 프로그램을 주기적으로 반복적으로 처리하므로 중복 서버라고 합니다. 동시 서버와 중복 서버에는 각각 장단점이 있습니다. 동시 서버는 클라이언트 프로그램의 응답 속도를 향상시킬 수 있지만 시스템 스케줄링의 오버헤드가 증가합니다. 서버는 정반대이므로 사용자가 동시 서버를 사용할지, 중복 서버를 사용할지 결정할 때는 실제 애플리케이션을 기반으로 결정해야 합니다.
3.5 데이터 전송──send() 및 recv()
연결이 설정되면 데이터를 전송할 수 있습니다. 일반적으로 사용되는 시스템 호출에는 send() 및 recv()가 있습니다.
send() 호출은 다음과 같습니다. 연결 번호 s를 지정하는 데 사용됩니다. 데이터그램 또는 스트림 소켓에서 출력 데이터를 전송하며 형식은 다음과 같습니다.
int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int 플래그);
매개변수 s는 연결된 로컬 소켓 설명자입니다. buf는 전송된 데이터가 포함된 버퍼를 가리키며, 해당 길이는 전송 여부와 같은 전송 제어 방법을 지정합니다. out-of-band 데이터 등이 발생하면 send()는 보낸 총 바이트 수를 반환합니다.
send()에 대한 자세한 설명은 5.2.19를 참조하세요.
recv() 입력 데이터는 숫자 s로 지정된 연결된 데이터그램 또는 스트림 소켓에서 수신됩니다. 형식은 다음과 같습니다.
int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int 플래그) ;
매개변수 s는 연결된 소켓 설명자입니다. buf는 길이가 len으로 지정되는 입력 데이터를 수신하는 버퍼에 대한 포인터입니다. 플래그는 대역 외 데이터 수신 여부 등의 전송 제어 방법을 지정합니다. 오류가 발생하지 않으면 recv()는 수신된 총 바이트 수를 반환합니다. 연결이 닫히면 0이 반환됩니다. 그렇지 않으면 SOCKET_ERROR를 반환합니다.
recv()에 대한 자세한 설명은 5.2.16을 참고하세요.
3.6 입출력 다중화─select()
select() 호출은 하나 이상의 소켓 상태를 감지하는 데 사용됩니다. 각 소켓에 대해 이 호출은 읽기, 쓰기 또는 오류 상태 정보를 요청할 수 있습니다. 주어진 상태를 요청하는 소켓 세트는 fd_set 구조로 표시됩니다. 반환 시 이 구조는 특정 조건을 충족하는 소켓의 하위 집합을 반영하도록 업데이트됩니다. 동시에 select() 호출은 조건을 충족하는 소켓 수를 반환합니다.
int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * Exceptfds, const struct timeval FAR * timeout)
nfds 매개변수는 소켓 설명자의 값을 지정합니다. 도메인을 확인하면 이 변수는 일반적으로 무시됩니다.
readfds 매개변수는 읽고 테스트할 소켓 설명자 집합에 대한 포인터를 가리키며 호출자는 여기에서 데이터를 읽기를 원합니다. writefds 매개변수는 쓰기 테스트를 위한 소켓 설명자 세트에 대한 포인터입니다. Exceptfds 오류를 감지하기 위한 소켓 설명자 집합에 대한 포인터입니다. timeout은 select() 함수가 기다리는 최대 시간을 가리킵니다. NULL로 설정되면 차단 작업입니다. select()는 fd_set 구조에 포함된 준비된 소켓 설명자의 총 개수를 반환하거나, 오류가 발생하면 SOCKET_ERROR를 반환합니다.
select()에 대한 자세한 설명은 5.2.18을 참고하세요.
3.7 소켓 닫기──closesocket()
closesocket()은 소켓 s를 닫고 소켓에 할당된 리소스를 해제합니다. s에 열린 TCP 연결이 포함되어 있으면 연결이 해제됩니다. closesocket()의 호출 형식은 다음과 같습니다.
BOOL PASCAL FAR closesocket(SOCKET s)
매개변수 s는 닫힐 소켓 설명자입니다. 오류가 발생하지 않으면 closesocket()은 0을 반환합니다. 그렇지 않으면 반환 값 SOCKET_ERROR가 반환됩니다.
closesocket()에 대한 자세한 설명은 5.2.3을 참고하시기 바랍니다.
2.4 일반적인 소켓 호출 프로세스의 예
앞서 언급했듯이 TCP/IP 프로토콜의 응용은 일반적으로 클라이언트/서버 모드를 채택하므로 실제 응용에서는 클라이언트와 서버라는 두 가지 프로세스가 있어야 하며 서버가 시작됩니다. 먼저 시스템 호출 타이밍 다이어그램은 다음과 같습니다.
연결 지향 프로토콜(예: TCP)의 소켓 시스템 호출은 그림 2.1에 나와 있습니다.
서버는 accept() 호출을 완료하고 대기 상태에 들어갈 때까지 먼저 시작해야 합니다. 상태, 고객 요청을 수신합니다. 클라이언트가 이전에 시작된 경우 connect()는 오류 코드를 반환하고 연결에 실패합니다.
그림 2.1 연결 지향 소켓 시스템 호출 타이밍 다이어그램
연결 프로토콜이 없는 소켓 호출은 그림 2.2에 표시됩니다.
그림 2.2 없음 소켓 호출 시퀀스 다이어그램
연결 없는 서버도 먼저 시작해야 합니다. 그렇지 않으면 클라이언트 요청이 서비스 프로세스로 전송되지 않습니다. 연결 없는 클라이언트는 connect()를 호출하지 않습니다. 따라서 데이터를 보내기 전에는 클라이언트와 서버 사이에 완전한 상관관계가 성립되지 않고, 소켓()과 바인드()를 통해 반상관관계가 성립된다. 데이터를 보낼 때 송신자는 로컬 소켓 번호 외에 수신자의 소켓 번호를 지정해야 하므로 데이터 송신 및 수신 프로세스 중에 전체 상관 관계를 동적으로 설정해야 합니다.
예
이 예는 연결 프로토콜을 지향하는 클라이언트/서버 모델을 사용합니다. 프로세스는 그림 2.3에 나와 있습니다.
그림 2.3 연결 지향 응용 흐름도
서버 측 프로그램:
/* 파일 이름: streams.c */
#include
#include
#define TRUE 1
/* 이 프로그램은 소켓을 설정한 다음 무한 루프를 시작합니다. 루프를 통해 연결을 받을 때마다 메시지를 인쇄합니다. 연결이 끊어지거나 종료 메시지를 받으면 연결이 종료되고 프로그램은 새로운 연결을 받습니다. 명령줄 형식은 다음과 같습니다. streams */
main(
{
int sock, length;
struct sockaddr_in server;
struct sockaddr tcpaddr
int msgsock;
char buf[1024];
int rval, len; >/ * 소켓 만들기*/
sock = 소켓(AF_INET, SOCK_STREAM, 0)
if (sock < 0) {
perror(“스트림 소켓 열기 ");
exit(1);
}
/* 임의의 포트를 사용하여 소켓 이름 지정*/
server.sin_family = AF_INET;
server.sin_port = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror(“바인딩 스트림 소켓”);
exit(1);
}
/* 지정된 포트 번호를 찾아 인쇄합니다. */
length = sizeof(server);
if (getsockname(sock, (struct sockaddr *)&server, &length) < 0) {
perror(“ 소켓 이름 가져오기");
exit(1);
}
printf("소켓 포트 #%d
", ntohs(server.sin_port)) ;
/* 연결 수신 시작*/
listen(sock, 5)
len = sizeof(struct sockaddr); do {
msgsock = accept(sock, (struct sockaddr *)&tcpaddr, (int *)&len)
if (msgsock == -1)
perror( "수락");
그렇지 않으면{
memset(buf, 0, sizeof(buf))
if ((rval = recv(msgsock, buf, 1024) )) < 0)
perror(“스트림 메시지를 읽는 중”);
if (rval == 0)
printf(“연결 종료
”) ;
else
printf(“-->%s
”, buf)
}while (rval != 0); 🎜>closesocket(msgsock);
} while (TRUE);
/* 이 프로그램에는 이미 무한 루프가 있으므로 소켓 "sock"은 닫힌 것으로 표시되지 않습니다. 그러나 프로세스가 종료되거나 정상적으로 종료되면 모든 소켓이 자동으로 닫힙니다. */
exit(0);
}
클라이언트 프로그램:
/* 파일 이름: streamc.c */
#include
#include
#define DATA “half a League, half a League ...”
/* 이 프로그램은 소켓을 설정한 후 The와 통신합니다. 명령줄에 소켓 연결이 제공됩니다. 연결이 끝나면 연결에 대해
메시지를 보낸 다음 소켓을 닫습니다. 명령줄 형식은 다음과 같습니다. streamc 호스트 이름 포트 번호
포트 번호는 서버 프로그램의 포트 번호와 동일해야 합니다*/
main(argc, argv)
int argc;
char *argv[ ];
int sock;
struct sockaddr_in 서버; >structhostent *hp, * gethostbyname( ;
char buf[1024];
/* 소켓 만들기*/
sock = 소켓(AF_INET, SOCK_STREAM , 0);
if (sock < 0) {
perror(“스트림 소켓 열기”);
}
/* 명령줄에 지정된 이름을 사용하여 소켓을 연결합니다*/
server.sin_family = AF_INET; >
hp = gethostbyname(argv[1]);
if (hp == 0) {
fprintf(stderr, “%s: 알 수 없는 호스트
”, argv [1]);
exit(2);
}
memcpy((char*)&server.sin_addr, (char*)hp->h_addr, hp ->h_length);
sever.sin_port = htons(atoi(argv[2]))
if (connect(sock, (struct sockaddr*)&server, sizeof (서버)) < 0) {
perror(“스트림 소켓 연결 중”)
exit(3)
if (sock, DATA, sizeof(DATA))
perror(“스트림 소켓에서 전송 중”)
closesocket(sock); >exit(0);
}
2.5 일반적인 예제 프로그램
이전 섹션에서는 간단한 소켓 프로그램 예제를 소개했습니다. 이 예에서 우리는 소켓 프로그래밍을 사용하는 데 거의 패턴이 있음을 알 수 있습니다. 즉, 모든 프로그램은 거의 예외 없이 동일한 기능을 동일한 순서로 호출합니다. 따라서 우리는 몇 가지 간단한 기능을 제공하는 중간 계층을 설계하는 것을 상상할 수 있습니다. 프로그램은 인터넷에서 데이터 전송을 실현하기 위해 이러한 기능만 호출하면 됩니다. 프로그래머는 소켓 프로그램 설계 세부 사항에 대해 너무 많이 신경 쓸 필요가 없습니다.
이 섹션에서는 상위 계층에 몇 가지 간단한 기능을 제공하는 일반적인 네트워크 프로그램 인터페이스를 소개합니다. 프로그래머는 이러한 기능을 사용하여 대부분의 온라인 시험을 완료할 수 있습니다. 이러한 함수는 상위 계층에서 소켓 프로그래밍을 분리합니다. 이는 연결 지향 스트리밍 소켓과 비차단 작업 메커니즘을 사용하여 네트워크 메시지를 쿼리하고 이에 따라 응답하기만 하면 됩니다. 이러한 기능은 다음과 같습니다.
l InitSocketsStruct: 소켓 구조를 초기화하고 서비스 포트 번호를 얻습니다. 클라이언트 프로그램에서 사용됩니다.
l InitPassiveSock: 소켓 구조를 초기화하고 서비스 포트 번호를 얻은 후 메인 소켓을 설정합니다. 서버 프로그램에서 사용됩니다.
l CloseMainSock: 메인 소켓을 닫습니다. 서버 프로그램에서 사용됩니다.
l CreateConnection: 연결을 설정합니다. 클라이언트 프로그램에서 사용됩니다.
l AcceptConnection: 연결을 수락합니다. 서버 프로그램에서 사용됩니다.
l CloseConnection: 연결을 종료합니다.
l QuerySocketsMsg: 소켓 메시지를 쿼리합니다.
l SendPacket: 데이터를 보냅니다.
l RecvPacket: 데이터를 수신합니다.
2.5.1 헤더 파일
/* 파일 이름: tcpsock.h */
/* 헤더 파일에는 소켓 프로그램에서 자주 사용하는 시스템 헤더 파일이 포함되어 있습니다. 에 제공된 헤더 파일은 SCO Unix의 헤더 파일이며(다른 버전의 Unix의 헤더 파일은 약간 다를 수 있음), 우리가 제공하는 함수 설명뿐만 아니라 두 가지 자체 데이터 구조와 해당 인스턴스 변수를 정의합니다. */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct SocketsMsg{ /* 소켓 메시지 구조*/
int AcceptNum ; /* 수신 대기 중인 외부 연결이 있는지 여부를 나타냅니다.*/
int ReadNum /* 읽기 대기 중인 외부 데이터가 있는 연결 수*/
int ReadQueue[32] ; /* 읽기를 기다리는 외부 데이터가 있는 연결 큐가 있습니다*/
int WriteNum /* 데이터를 보낼 수 있는 연결 수*/
int WriteQueue[32]; /* 데이터를 보낼 수 있는 연결 큐 */
int ExceptNum /* 예외가 있는 연결 수*/
int ExceptQueue[32] /* 예외가 있는 연결 큐*/
} SocketsMsg;
typedef struct Sockets { /* 소켓 구조*/
int DaemonSock;
int SockNum ; /* 데이터 소켓 수*/
int Sockets[64]; /* 데이터 소켓 배열*/
fd_set readfds, writefds, Exceptfds; 감지된 쓰기, 예외 소켓 수집 */
int Port; /* 포트 번호*/
} 소켓
/* 전역 변수 */
SocketsMsg SockMsg;
int InitSocketsStruct(char * 서비스 이름);
int InitPassiveSock(char * 서비스 이름)
void CloseMainSock() ;
int CreateConnection(struct in_addr *sin_addr);
int AcceptConnection(struct in_addr *IPaddr)
int CloseConnection(int Sockno); int QuerySocketsMsg()
int SendPacket(int Sockno, void *buf, int len)
int RecvPacket(int Sockno, void *buf, int size); >2.5.2 함수 소스 파일
/* 파일명 : tcpsock.c */
/* 이 파일은 9개 함수의 소스 코드를 제공하며 그 중 일부는 중국어 주석을 제공합니다*/
#include "tcpsock.h"
int InitSocketsStruct(char * servicename )
/* 소켓 구조 초기화. 성공하면 1을 반환하고 그렇지 않으면 오류 코드(<0)를 반환합니다. */
/* 이 함수는 활성 소켓 프로그램만 필요한 클라이언트에 사용됩니다. 서비스 정보를 얻기 위해 사용됩니다. 서비스 정의
는 /etc/services 파일*/
{
structservent
struct sockaddr_in serv_addr; >
if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1)
}
bzero ((char *)&Mysock, sizeof(Sockets))
Mysock.Port = servrec->s_port; /* 네트워크 바이트 순서의 서비스 포트 */
return(1 );
}
int InitPassiveSock(char * servicename)
/* 패시브 소켓 초기화에 성공하면 1을 반환하고 그렇지 않으면 오류 코드(< 0) */
/* 이 함수는 패시브 소켓이 필요한 서버 프로그램에 사용되며 서비스 정보를 얻는 것 외에도
패시브 소켓을 설정합니다. */
{
int mainsock, flag=1;
structservent
struct sockaddr_in serv_addr; 🎜>if ((servrec = getservbyname(servicename, "tcp")) == NULL) {
return(-1)
}
bzero((char *)&Mysock, sizeof(Sockets))
Mysock.Port = servrec->s_port /* 네트워크 바이트 순서의 서비스 포트 */
if((mainsock = 소켓(AF_INET, SOCK_STREAM, 0)) < 0) {
return(-2);
}
bzero((char *)&serv_addr, sizeof(serv_addr));
AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); >serv_addr.sin_port = servrec->s_port;
if (bind(mainsock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
close(mainsock) ;
return(-3);
}
if (listen(mainsock, 5) == -1) { /* 활성 소켓이 됩니다. 패시브 소켓, 연결 수신 준비됨*/
close(mainsock)
return(-4)
}
/* 이 소켓을 비차단 소켓으로 설정합니다. */
if (ioctl(mainsock, FIONBIO, &flag) == -1) {
close(mainsock); >return(-5);
}
Mysock.DaemonSock = mainsock;
FD_SET(mainsock, &Mysock.readfds) /* " 읽을 수 있는" 메인 소켓 */
FD_SET(mainsock, &Mysock.justfds); /* 메인 소켓의 예외 이벤트에 대한 관심을 선언합니다*/
return(1);
}
void CloseMainSock()
/* 메인 소켓을 닫고 그 위에 있는 이벤트 선언을 지웁니다. 프로그램이 끝나기 전에 메인 소켓을 닫는 것이 좋습니다*/ )
FD_CLR(Mysock.DaemonSock, &Mysock.justfds)
}
int CreateConnection(struct in_addr *sin_addr)
/ * sin_addr에 IP 주소가 있는 원격 호스트에 대한 연결을 생성합니다.
Param: sin_addr은 네트워크 바이트 순서로 IP 주소를 나타냅니다.
성공하면 이 연결을 나타내는 소켓 번호를 반환하고,
그렇지 않으면 오류 코드(<0)를 반환합니다. */
{
struct sockaddr_in 서버 ; /* 서버 주소 */
int tmpsock, flag= 1, i;
if ((tmpsock = 소켓(AF_INET, SOCK_STREAM, 0)) < 0) 🎜>
return(-1);
server.sin_family = AF_INET;
server.sin_port =
server.sin_addr; s_addr = sin_addr->s_addr;
/ * 이 소켓을 비차단 소켓으로 설정합니다. */
if (ioctl(tmpsock, FIONBIO, &flag) == - 1) {
close(tmpsock);
return(-2);
}
/* 서버에 연결합니다. /
if (connect(tmpsock, (struct sockaddr *) &server, sizeof(server)) < 0) {
if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS )) {
/* 오류 코드가 EWOULDBLOCK 및 EINPROGRESS 이면 시스템이 나중에 소켓에 대한 연결을 계속 설정하므로 소켓을 닫을 필요가 없습니다. 소켓이 "쓰기 가능한"지 여부를 감지하기 위해 select() 함수를 사용하여 결정됩니다. */
close(tmpsock);
return(-3) /* 연결 오류 */
}
} 🎜>
FD_SET(tmpsock, &Mysock.readfds)
FD_SET(tmpsock, &Mysock.writefds);FD_SET(tmpsock, &Mysock.justfds);
나는 = 0;
while (Mysock.Sockets[i] != 0) i++; /* 빈 소켓 위치 찾기 */
if (i >= 64) {
close(tmpsock);
return(-4); /* 연결이 너무 많습니다 */
}
Mysock.Sockets[i] = tmpsock;
Mysock.SockNum++;
반환(i);
}
int AcceptConnection(struct in_addr *IPaddr)
/* 연결을 수락합니다. 성공하면 데이터 소켓 번호를 반환하고, 그렇지 않으면 -1을 반환합니다. */
{
int newsock, len, flag=1, i;
struct sockaddr_in addr;
len = sizeof(addr);
bzero((char *)&addr, len);
if ((newsock = accept(Mysock.DaemonSock, &addr, &len)) == -1)
return(-1); /* 오류를 수락합니다. */
/* 이 소켓을 비차단 소켓으로 설정합니다. */
ioctl(newsock, FIONBIO, &flag);
FD_SET(newsock, &Mysock.readfds);
FD_SET(newsock, &Mysock.writefds);
FD_SET(newsock, &Mysock.justfds);
/* 매개변수에 IP 주소를 반환합니다. */
IPaddr->s_addr = addr.sin_addr.s_addr;
나는 = 0;
while (Mysock.Sockets[i] != 0) i++; /* 빈 소켓 위치 찾기 */
if (i >= 64) {
close(newsock);
return(-4); /* 연결이 너무 많습니다 */
}
Mysock.Sockets[i] = newock;
Mysock.SockNum++;
반환(i);
}
int CloseConnection(int Sockno)
/* Sockno가 지정한 연결을 닫습니다. */
{
int retcode;
if ((Sockno >= 64) (Sockno < 0) (Mysock.Sockets[Sockno] == 0))
return(0);
retcode = close(Mysock.Sockets[Sockno]);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.readfds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.writefds);
FD_CLR(Mysock.Sockets[Sockno], &Mysock.justfds);
Mysock.Sockets[Sockno] = 0;
Mysock.SockNum--;
반품(재코드);
}
int QuerySocketsMsg()
/* 쿼리 소켓 메시지. 성공하면 메시지 번호를 반환하고, 그렇지 않으면 -1을 반환합니다.
SockMsg 구조체에 저장된 메시지 정보입니다. */
{
fd_set rfds, wfds, efds;
int retcode, i;
struct timeval TimeOut;
rfds = Mysock.readfds;
wfds = Mysock.writefds;
efds = Mysock.readfds;
TimeOut.tv_sec = 0; /* 立即返回,不阻塞。*/
TimeOut.tv_usec = 0;
bzero((char *)&SockMsg, sizeof(SockMsg));
if ((retcode = select(64, &rfds, &wfds, &efds, &TimeOut)) == 0)
return(0);
if (FD_ISSET(Mysock.DaemonSock, &rfds))
SockMsg.AcceptNum = 1; /* 일부 클라이언트 호출 서버. */
for (i=0; i<64; i++) /* 메시지의 데이터 */
{
if ((Mysock.Sockets[ i] > 0) && (FD_ISSET(Mysock.Sockets[i], &rfds)))
SockMsg.ReadQueue[SockMsg.ReadNum++] = i;
}
for (i=0; i<64; i++) /* 데이터 출력 준비 메시지 */
{
if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &wfds)))
SockMsg.WriteQueue[SockMsg.WriteNum++] = i;
}
if (FD_ISSET(Mysock.DaemonSock, &efds))
SockMsg.AcceptNum = -1; /* 서버 소켓 오류. */
for (i=0; i<64; i++) /* 오류 메시지 */
{
if ((Mysock.Sockets[i ] > 0) && (FD_ISSET(Mysock.Sockets[i], &efds)))
SockMsg.ExceptQueue[SockMsg.ExceptNum++] = i;
}
반품(재코드);
}
int SendPacket(int Sockno, void *buf, int len)
/* 패킷을 보냅니다. 성공하면 전송 데이터 수를 반환하고, 그렇지 않으면 -1을 반환합니다. */
{
int actlen;
if ((Sockno >= 64) (Sockno < 0) (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = send(Mysock.Sockets[Sockno], buf, len, 0)) < 0)
return(-1);
반환(actlen);
}
int RecvPacket(int Sockno, void *buf, int size)
/* 패킷을 받습니다. 성공하면 수신 데이터 수를 반환하고, 연결이
피어에 의해 종료되면 0을 반환하고, 그렇지 않으면 0-errno를 반환합니다. */
{
int actlen;
if ((Sockno >= 64) (Sockno < 0) (Mysock.Sockets[Sockno] == 0))
return(0);
if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)
return(0-errno) ;
return(actlen); /* actlen은 수신된 데이터 길이가 0이면 상대방에 의해 연결이 종료되었음을 나타냅니다. */
}
2.5.3 간단한 서버 프로그램 예제
/* 파일명 : server.c */
/* 이것은 매우 수동 소켓을 초기화하고 루프에서 연결 수신을 기다리는 서버 프로그램을 반복하기만 하면 됩니다. 연결이 수신되면 데이터 소켓 일련 번호와 클라이언트의 IP 주소가 표시되고, 데이터 소켓에 데이터가 도착하면 데이터가 수신되고 연결의 데이터 소켓 일련 번호와 수신된 문자열이 표시됩니다. */
#include "tcpsock.h"
main(argc, argv)
int argc;
char **argv;
{
struct in_addr sin_addr;
int retcode, i;
char buf[32];
/* 서버 프로그램의 경우 종종 무한 루프 상태는 사용자가 프로세스를 적극적으로 종료하거나 시스템이 종료될 때만 종료됩니다. kill을 사용하여 강제 종료되는 서버 프로그램의 경우 메인 소켓이 닫히지 않고 리소스가 적극적으로 해제되지 않으므로 이후 서버 프로그램을 다시 시작하는 데 영향을 미칠 수 있습니다. 따라서 메인 소켓을 적극적으로 닫는 것이 좋은 습관입니다. 다음 명령문은 프로그램이 먼저 CloseMainSock() 함수를 실행하여 SIGINT, SIGQUIT 및 SIGTERM과 같은 신호를 수신할 때 기본 소켓을 닫은 다음 프로그램을 종료하도록 합니다. 따라서 서버 프로세스를 강제 종료하기 위해 kill을 사용하는 경우 먼저 kill -2 PID를 사용하여 서버 프로그램에 메인 소켓을 닫으라는 메시지를 보낸 다음 kill -9 PID를 사용하여 프로세스를 강제 종료해야 합니다. */
(void) signal(SIGINT, CloseMainSock);
(void) signal(SIGQUIT, CloseMainSock)
(void) signal(SIGTERM, CloseMainSock);
if ((retcode = InitPassiveSock("TestService")) < 0) {
printf("InitPassiveSock: 오류 코드 = %d
", retcode);
exit(-1);
}
while (1) {
retcode = QuerySocketsMsg() /* 쿼리 네트워크 메시지 */
if (SockMsg.AcceptNum == 1) { /* 수신 대기 중인 외부 연결이 있습니까? */
retcode = AcceptConnection(&sin_addr);
printf("retcode = %d, IP = %s
", retcode, inet_ntoa(sin_addr.s_addr)); >
}
else if (SockMsg.AcceptNum == -1) /* 기본 소켓 오류입니까? */
printf("데몬 소켓 오류.
");
for (i=0; i
if ((retcode = RecvPacket(SockMsg.ReadQueue[i] , buf, 32)) > 0)
printf("sockno %d Recv string = %s
", SockMsg.ReadQueue[i], buf)
else / * 반환된 데이터 길이는 0입니다. 이는 연결이 중단되고 소켓이 닫혔음을 나타냅니다. */
CloseConnection(SockMsg.ReadQueue[i])
}
} /* end while */
}
2.5.4 간단한 클라이언트 프로그램 예제
/* 파일명 : client.c */
/* 클라이언트 프로그램이 실행되면 먼저 데이터 구조를 초기화한 다음 사용자가 명령을 입력할 때까지 기다립니다.
conn(ect): 서버와 연결을 설정합니다. >send: 지정된 연결로 데이터 보내기;
clos(e): 지정된 연결을 닫습니다.
quit: 클라이언트 프로그램 종료
*/
#include "tcpsock.h"
<.>
main(argc, argv)
int argc;
{
char cmd_buf[16];
struct in_addr sin_addr;
int sockno1,
char *buf = "이것은 문자열입니다. for test.";
sin_addr.s_addr = inet_addr("166.111.5.249"); /* 서버 프로그램을 실행하는 호스트의 IP 주소*/
if ((retcode = InitSocketsStruct("TestService")) < 0) { /* 데이터 구조 초기화*/
printf("InitSocketsStruct: error code = %d
", retcode);
exit(1);
}
while (1) {
printf(">"); 🎜>gets(cmd_buf);
if ( !strncmp(cmd_buf, "conn", 4)) {
retcode = CreateConnection(&sin_addr) /* 연결 만들기*/
printf("반환 코드: %d
" , retcode);
}
else if(!strncmp(cmd_buf, "send", 4)) {
printf("소켓 번호:");
scanf("%d", &sockno1);
retcode = SendPacket(sockno1, buf, 26); */
printf("반환 코드: % d
", retcode, sizeof(buf))
}
else if (!strncmp(cmd_buf, "닫기", 4)) {
printf("소켓 번호:");
scanf("%d", &sockno1);
retcode = CloseConnection(sockno1) /* 연결 종료*/
printf("반환 코드: %d
", retcode);
}
else if (!strncmp(cmd_buf, "quit", 4))
exit(0);
else
putchar('
위 내용은 소켓 프로그래밍 원리의 내용입니다. 더 많은 관련 글은 PHP 중국어를 참고해주세요. 홈페이지(m.sbmmt.com)