0:TCP/IP協議棧與資料包封裝CP/IP網路通訊協定棧分為應用程式層(Application)、傳輸層(Transport)、網路層(Network)和鏈路層(Link)四層。
不同的協議層對資料包有不同的稱謂,
在傳輸層叫做段(segment),
在網路層叫做資料報(datagram),在
鏈路層叫做幀(frame)。資料封裝成幀後發到傳輸介質上,到達目的主機後每層協議再剝掉相應的首部,最後將應用程式層資料交給應用程式處理。。IP地址是標識網路中不同主機的地址,而連接埠號碼就是同一台主機上標識不同進程的地址,
IP地址和連接埠號碼合起來標識網路中唯一的進程。
1:基於TCP服務端和用戶端系統調用
由於用戶端不需要固定的連接埠號碼,因此不必調用bind(),用戶端的連接埠號碼由核心自動分配。注意,用戶端不是不允許調用bind(),只是沒有必要調用bind()固定一個連接埠號碼,伺服器也不是必須調用bind(),但如果伺服器不調用bind(),核心會自動給伺服器分配監聽連接埠,每次啟動伺服器時連接埠號碼都不一樣,用戶端要串連伺服器就會遇到麻煩。
2:socket 地址說明Generic Socket Address Structures通用socket address
struct sockaddr {sa_family_t sa_family; /* Address family (AF_* constant) */char sa_data[14]; /* Socket address (size variesaccording to socket domain) */};struct sockaddr_in { /* IPv4 socket address */sa_family_t sin_family; /* Address family (AF_INET) */in_port_t sin_port; /* Port number */struct in_addr sin_addr; /* IPv4 address */unsigned char __pad[X]; /* Pad to size of 'sockaddr'structure (16 bytes) */};struct in_addr { /* IPv4 4-byte address */in_addr_t s_addr; /* Unsigned 32-bit integer */};
在linux socket編程中使用的socket地址都為通用格式即:struct sockaddr
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);Returns 0 on success, or –1 on error
對於IPv4,family參數指定為AF_INET。
對於TCP協議,type參數指定為SOCK_STREAM,表示面向流的傳輸協議。
對於UDP協議,type參數指定為SOCK_DGRAM,表示面向資料報的傳輸協議。
protocol參數的介紹從略,指定為0即可。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Returns 0 on success, or –1 on errorint listen(int sockfd, int backlog); Returns 0 on success, or –1 on error int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Returns file descriptor on success, or –1 on errorint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);Returns 0 on success, or –1 on error 比如在bind(),accept(),connect()中的函數中都使用的是struct sockaddr地址,但是在實際的使用中使用的是:struct sockaddr_in地址。因此代碼中常用的地址格式定義如下:struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("192.168.100.244");
servaddr.sin_port = htons(SERV_PORT);struct sockaddr_in中的成員struct in_addr sin_addr表示32位的IP地址。實際中多數使用點分十進位的字串表示IP地址,以下函數可以實現字串表示和in_addr表示之間轉換。
字串轉in_addr的函數:
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr);
int inet_pton(int family, const char *strptr, void *addrptr);
in_addr轉字串的函數:
char *inet_ntoa(struct in_addr inaddr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
3:TCP服務端和用戶端代碼架構。服務端:通過server ip和port 返回socketID
int getServerSokcketId(int argc, char *argv[]){struct sockaddr_in servaddr;int listenfd,optval = 1;;if (argc != 3){fputs("usage: ./server serverIp serverPort\n", stderr);exit(1);}listenfd = socket(AF_INET, SOCK_STREAM, 0);if(listenfd == -1){ close(listenfd);perror("create server socket ....");exit(0);}bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_addr.s_addr = inet_addr(argv[1]);servaddr.sin_port = htons(atoi(argv[2])); if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval,sizeof(optval)) == -1) { close(listenfd);perror("server socket setsockopt...."); return -1; }if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1){perror("server bind ...."); close(listenfd);exit(0);}if(listen(listenfd, 20) == -1){perror("server listen ...."); close(listenfd);exit(0);}printf("Accepting connections ...\n");return listenfd;}
main函數調用,同時完成對用戶端的請求處理。
int main(int argc, char *argv[]){struct sockaddr_in cliaddr;socklen_t cliaddr_len;int listenfd, acceptFd; listenfd = getServerSokcketId(argc,argv);while(1){cliaddr_len = sizeof(cliaddr);acceptFd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); /* Wait for connection */if (acceptFd == -1){syslog(LOG_ERR, "Failure in accept(): %s",strerror(errno));continue; /* Try next */}else{printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));}#if 0switch (fork()) //使用多進程處理使用者的請求{ /* Create child for each client */case -1:printf("------------------------------fork() return error\n");syslog(LOG_ERR, "111Can't create child (%s)",strerror(errno));close(acceptFd); /* Give up on this client */break; /* May be temporary; try next client */case 0: /* Child */close(listenfd); /* Don't need copy of listening socket */handleRequest(acceptFd);printf("----handleRequest execute over--------------------\n");_exit(EXIT_SUCCESS);default: /* Parent */close(acceptFd); /* Don't need copy of connected socket */break; /* Loop to accept next connection */}#endifclientProcess(acceptFd);//使用多執行緒用戶端的請求。 }}
其中函數clientProcess(acceptFd);定義如下:
void *thr_fn1(int cfd){ssize_t numRead,numSocketRead,numSocketWrite;char message[1000];while(1){if((numSocketRead = read(cfd, &message, sizeof(message))) > 0){//此處對收到的message進行處理numSocketWrite=write(cfd, &message, sizeof(message));if ((numSocketWrite != sizeof(message)) || numSocketWrite == -1) {syslog(LOG_ERR, "write() failed: %s", strerror(errno));return(EXIT_FAILURE);}}f(numSocketRead <= 0){printf("process pid:%d exit\n",getpid());close(cfd);syslog(LOG_ERR, "Error from read(): %s", strerror(errno));return(EXIT_FAILURE);}}}void clientProcess(int cfd){ pthread_t tid1;pthread_create(&tid1, NULL, thr_fn1, cfd);}
用戶端代碼
以下函數返回用戶端socketID跟服務端類似。
int getClietnSocketId(int argc, char *argv[]){struct sockaddr_in servaddr;char buf[MAXLINE];int sockfd, n;if (argc != 3){fputs("usage: ./client serverIp serverPort\n", stderr);exit(1);}sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd == -1){perror("create socket....\n");}bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(atoi(argv[2]));inet_aton(argv[1], &servaddr.sin_addr.s_addr);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr.s_addr) <= 0){printf("set ip address error!\n");exit(0);}//inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr.s_addr);if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))==-1){perror("connect....");exit(0);}return sockfd;}
main函數
int main(int argc, char *argv[]){int sfd=0,numRead;char parentMsg[1000]; char message[1000]; char buf[BUF_SIZE]; memset(&message,0,sizeof(message)); memset(&parentMsg,0,sizeof(parentMsg));sfd= getClietnSocketId(argc,argv); if (sfd == -1) { errExit("inetConnect"); } switch (fork()) { case -1: errExit("fork"); case 0: /* Child: read server's response, echo on stdout */ memset(&message,0,sizeof(message)); for (;;) { //memset(&message,0,sizeof(struct fileMessage)); mutex = pthread_mutex_lock(&mtx); if (mutex != 0) printf("pthread_mutex_lock"); if((numRead = read(sfd, &message, sizeof(message)))>0) { } mutex = pthread_mutex_unlock(&mtx); if (mutex!= 0) printf("pthread_mutex_unlock"); if (numRead <= 0) /* Exit on EOF or error */ { printf("server has closed!..%s\n",strerror(errno)); break; } } printf("-------------fork exit, message: %s----------------------------------\n",strerror(errno)); _exit(EXIT_SUCCESS); default: /* Parent: write contents of stdin to socket */ while(1) { if((numRead = read(STDIN_FILENO, buf, BUF_SIZE))>0) { if((write(sfd, &parentMsg, sizeof(parentMsg))) != sizeof(parentMsg)) { fatal("write() failed"); close(sfd) } } } } exit(EXIT_SUCCESS); }}
4:執行結果