(點擊上方公眾號,可快速關注)
英文:hxf0223
連結:http://blog.chinaunix.net/uid-670184-id-5754181.html
TCP/IP協議及socket封裝socket編程的基本流程
socket連接的建立(3次握手)
socket連接的斷開(3次握手)
由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。這個原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的連接。收到一個 FIN只意味著這一方向上沒有數據流動,一個TCP連接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。
(1)客戶端A發送一個FIN,用來關閉客戶A到伺服器B的數據傳送(報文段4)。
(2)伺服器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1(報文段5)。和SYN一樣,一個FIN將佔用一個序號。
(3)伺服器B關閉與客戶端A的連接,發送一個FIN給客戶端A(報文段6)。
(4)客戶端A發回ACK報文確認,並將確認序號設置為收到序號加1(報文段7)。
對應函數接口如圖:
socket編程之bind函數
int bind(int sockfd, const struct sockaddr *addr,socklen_t *addrlen);
功能描述:
當用socket()函數創建套接字以後,套接字在名稱空間(網絡地址族)中存在,但沒有任何地址給它賦值。bind()把用addr指定的地址賦值給用文件描述符代表的套接字sockfd。addrlen指定了以addr所指向的地址結構體的字節長度。一般來說,該操作稱為「給套接字命名」。
通常,在一個SOCK_STREAM套接字接收連接之前,必須通過bind()函數用本地地址為套接字命名。
備註:
調用bind()函數之後,為socket()函數創建的套接字關聯一個相應地址,發送到這個地址的數據可以通過該套接字讀取與使用。
備註:
bind()函數並不是總是需要調用的,只有用戶進程想與一個具體的地址或埠相關聯的時候才需要調用這個函數。如果用戶進程沒有這個需要,那麼程序可以依賴內核的自動的選址機制來完成自動地址選擇,而不需要調用bind()函數,同時也避免不必要的複雜度。在一般情況下,對於伺服器進程問題需要調用bind()函數,對於客戶進程則不需要調用bind()函數。
socket編程之accept函數
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
功能參數描述accept()系統調用主要用在基於連接的套接字類型,比如SOCK_STREAM和SOCK_SEQPACKET。它提取出所監聽套接字的等待連接隊列中第一個連接請求,創建一個新的套接字,並返回指向該套接字的文件描述符。新建立的套接字不在監聽狀態,原來所監聽的套接字也不受該系統調用的影響。
備註:
新建立的套接字準備發送send()和接收數據recv()。
參數:
sockfd, 利用系統調用socket()建立的套接字描述符,通過bind()綁定到一個本地地址(一般為伺服器的套接字),並且通過listen()一直在監聽連接;
伺服器代碼:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#define QUEUE 20
#define MYPORT 8887
#define BUFF_SIZE 1024
int main(int argc, char *argv[])
{
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 定義socket fd
// 定義sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT); // host to network short
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);// host to network long
// bind socket address to socket fd
if (bind(server_sockfd, (struct sockaddr*)&server_sockaddr, sizeof(server_sockaddr)) == -1) {
perror("bind");
exit(1);
}
if (listen(server_sockfd, QUEUE) == -1) {
perror("listen");
exit(2);
}
// 客戶端套接字
char buff[BUFF_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
// 監聽並返回客戶端fd
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if (conn < 0) {
perror("accept");
exit(3);
}
while (1) {
memset(buff, 0, sizeof(buff));
int len = recv(conn, buff, sizeof(buff), 0);
if (strcmp(buff, "exit\n") == 0)
break;
printf("server received:%s", buff);
send(conn, buff, len, 0);
}
close(conn);
close(server_sockfd);
return 0;
}
客戶端代碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MYPORT 8887
#define BUFF_SIZE 1024
int main()
{
int sock_client_fd = socket(AF_INET, SOCK_STREAM, 0);
// 定義server socket
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(0));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(MYPORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 伺服器ip
// 連接伺服器
if (connect(sock_client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
exit(1);
}
char sendbuff[BUFF_SIZE], recvbuff[BUFF_SIZE];
while (fgets(sendbuff, sizeof(sendbuff), stdin) != NULL){
send(sock_client_fd, sendbuff, strlen(sendbuff), 0); // 發送
if (strcmp(sendbuff, "exit\n") == 0) break;
recv(sock_client_fd, recvbuff, sizeof(recvbuff), 0);
printf("client received:%s", recvbuff);
memset(recvbuff, 0, sizeof(recvbuff));
memset(sendbuff, 0, sizeof(sendbuff));
}
close(sock_client_fd);
return 0;
}
關注「Linux愛好者」
看更多精選技術文章
↓↓↓