Socket程式設計(高併發TCP/UDP)
技術標籤:作業系統+網路
Socket程式設計(TCP/UDP) <sys/socket.h>
如有問題請多多指教,
首先我們需要知道tcp和udp的協議的特點。
1.Tcp模式(由客戶端連結伺服器)
伺服器端 客戶端
socket() 建立套接字 socket()建立套接字
bind() 命名套接字(繫結地址)
listen()建立監聽佇列
accept()接收連線(阻塞)connect()發起連線
recv() 接收資料 send()傳送資料
Send()傳送資料 recv()傳送資料
Close()關閉連線 close()關閉連結
對於每一個主機我們都有大端和小端的區別,為啥保證可以正常通訊,我們網路位元組序均使用了大端的方式。現在pc端大多數使用小端模式,因此我們的主機位元組序使用小端。
<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unsigned short int hostlong)
unsigned long int ntonl(unsigned long int netlong)
unsigned short int ntons (unsigned short int netshort)
例如:
htonl 表示 host to network long 即將長整型主機位元組序的資料轉換成網路位元組序。
ip地址轉換函式
通常,我們習慣用可讀性好的字串來表示ip地址,比如用點分十進位制字串表示IPv4地址,以及用十六進位制字串表示IPv6地址。但程式設計中我們需要將它們轉化成整數方能使用。
用點分十進位制字串表示的IPv4地址和網路位元組序整數表示的IPv4地址之間的轉換:
#include<arpa/inet.h>
int_addr_t inet_addr(const char * strptr);
int inet_aton(const char * cp,struct in_addr* inp);
char * inet_ntoa(struct in_addr in);
inet_addr 將用點分十進位制字串表示的IPv4地址轉換成網路位元組序整數表示的IPv4,失敗時返回 INADDR_NONE;
inet aton 函式和上面的一樣,但是將轉化的字串存放在 inp中,成功返回1,失敗返回0.
inet_ntoa 函式將用網路位元組序整數表示ipv4 轉化成 點分十進位制字串表示的IPv4地址但是要注意的是,該函式中,有靜態變數,因此inet_nota是不可重入的。
函式介紹:
int socket(int domain, int type, int protocol);
失敗返回 -1,成功返回監聽套接字
domain 設定套接字協議族, AF_UNIX UNIX本地域協議簇, AF_INET TCP/IPV4協議簇 AF_INET6 TCP/IPV6協議簇
type 設定套接字服務型別 SOCK_STREAM (流服務) SOCK_UGRAM(資料報)
protocol 一般設定為0,使用預設協議
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
失敗返回-1,成功返回0.
sockfd是監聽套接字描述符,addr是ip地址結構,addrlen是地址長度。
struct sockaddr_in {
short sin_family; //地址族 Socket只能用AF_INET
u_short sin_port; //埠
struct in_addr sin_addr; //IPv4網路地址
char sin_zero[8]; //
};
int listen(int sockfd, int backlog);
成功返回0,失敗返回-1,
sockfd 是監聽套接字,backlog 監聽佇列的最大長度。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd 是監聽套接字,addr 是連線的ip地址,addrlen 是長度。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd是監聽套接字,addr是被連線的ip地址,addrlen 是長度
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd是連線套接字,buf是收到的陣列,len是長度,flags 是標誌位
當返回值為0的時候就代表斷開連結。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd是連線套接字,buf是寫入的陣列,len是長度,flags是標誌位
int close(int fd);
fd是套接字
在tcp中,在沒有客戶端連線的時候,我們回阻塞在accept()函式,等待有人來連線我們。
程式碼如下:
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
//建立套接子
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd !=-1);
//指定ip + port
struct sockaddr_in caddr,saddr;// saddr, ser; caddr,cli
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000); //網路位元組序列 大端
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
listen(sockfd,5);
while(1)
{
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if( c < 0)
{
continue;
}
printf("accept c = %d\n",c);
char buff[128] = {0};
while(1)
{
int n = recv(c,buff,127,0);
if(n==0)
{
printf("one cli is over\n");
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
exit(0);
}
cli.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd !=-1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
char buff[128] = {0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("buff=%s\n",buff);
}
close(sockfd);
exit(0);
}
2.UDP模式:
伺服器 客戶端
socket()建立套接字 socket()建立套接字
bind()命名套接字
recvfrom() 收到資料 sendto() 傳送資料
sendto() 傳送資料 recvfrom() 收到資料
close() 關閉 close() 關閉
socket() bind() close() 函式和 tcp的操作一樣,不一樣的是 收發資料
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd 是監聽套接字,buf是傳送的資料,len 是長度, flags 是標誌位,
dest_addr是對方的ip地址,addrlen是ip地址的長度。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
sockfd是監聽套接字,buf是陣列,len 是長度,flags是標誌位,src_addr是對方的ip地址,addrlen是ip長度。
程式碼:
ser.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
while(1)
{
int len = sizeof(caddr);
char buff[128] = {0};
int n = recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
printf("recv:%s\n",buff);
sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
}
exit(0);
}
cli.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr* )&saddr,sizeof(saddr));
while(1)
{
char buff[128]={0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(caddr));
memset(buff,0,128);
int len = sizeof(saddr);
recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
printf("recv:%s\n",buff);
}
close(sockfd);
exit(0);
}
思考:
如果我們每次只接收一個位元組的資料,如果我們傳送hello tcp 和 udp 的結果是怎樣的呢?
在tcp 中
在udp中
3.多執行緒tcp
在tcp 中,當有一個客戶端連線伺服器的時候,我們會進入到迴圈的收發過程中,這時候當我們在去連結其他的客戶端的時候,就會發生阻塞,一直等待當我們正在收發的客戶端斷開連線後,才會連線入我們新的客戶端。這時候我們可以將連線的過程寫道主執行緒中,每次連線一個那我們就去將這個連線後的操作,交給一個執行緒去操作,以此保證不阻塞的操作。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int create_socket();
//多執行緒處理高併發問題
void *fun(void *arg)
{
int c= (int )arg;
while(1)
{
char buff[128] = {0};
int n= recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("recv(%d)=%s\n",c,buff);
send(c,"ok",2,0);
}
printf("one cli is over\n");
close(c);
}
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
pthread_t id;
pthread_create(&id,NULL,fun,(void *)c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
listen(sockfd,5);
return sockfd;
}
4.多程序tcp
多程序的思想和多執行緒一樣,但是在程序複製的時候,也會複製描述符,我們將沒有用的描述符進行關閉,在當我們某一個客戶端斷開連線的時候,這個程序就要結束,這時候就會變成僵死程序,我們需要用訊號進行處理。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
void sig_fun(int sig)
{
wait(NULL);
}
int create_socket();
//多程序處理高併發問題
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
signal(SIGCHLD,sig_fun);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
pid_t pid = fork();
assert(pid!=-1);
if(pid == 0)
{
close(sockfd);
while(1)
{
char buff[128] = {0};
int n= recv(c,buff,127,0);
if(n<=0)
{
break;
}
printf("recv=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
printf("onr cli is over\n");
exit(0);
}
close(c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
listen(sockfd,5);
return sockfd;
}