同一埠監聽tcp和udp請求
阿新 • • 發佈:2019-01-10
問題:
眾所周知,同一臺機器的同一個埠只可以被一個程序使用,一般用於tcp,或者udp。那一個程序使用同一個埠同時監聽tcp、udp請求,是否可以呢?答案:可以。
程式碼:
server
為了同時監聽,server使用select進行多路訪問控制。
server端程式碼如下:
/*
TCP
INET
use select
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define LENGTH_OF_LISTEN_QUEUE 20
#define SERVER_PORT 8888
#define MAXLINE 4096
#define MAX_FD_NUM 10
static int init_new_client(int client_fd);
static int remove_client(int client_fd);
static int get_max_fd(int fd);
static int client_fdset[MAX_FD_NUM];
int main(int argc, char** argv)
{
int tcp_fd, udp_fd, connfd, client_fd;
struct sockaddr_in servaddr;
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char recv_buff[4096];
char response[] = "recv well done.";
int recv_len;
fd_set readfd;
int ret;
int max_fd;
int i;
for (i = 0; i < MAX_FD_NUM; i++) {
client_fdset[i] = -1;
}
if ((tcp_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) {
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
if ((udp_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1 ) {
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVER_PORT);
if (bind(tcp_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("tcp bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
if (bind(udp_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("udp bind socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
if (listen(tcp_fd, LENGTH_OF_LISTEN_QUEUE) == -1) {
printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
printf("----------waiting for client's request---------\n");
while (1) {
/*每次呼叫select前,都要重置監聽的描述符*/
FD_ZERO(&readfd);
FD_SET(tcp_fd, &readfd);
FD_SET(udp_fd, &readfd);
for (i = 0; i < MAX_FD_NUM; i++) {
if (client_fdset[i] != -1) {
FD_SET(client_fdset[i], &readfd);
}
}
int fd = tcp_fd;
if (udp_fd > tcp_fd)
{
fd = udp_fd;
}
max_fd = get_max_fd(fd);
ret = select(max_fd + 1, &readfd, NULL, NULL, NULL);//有資料時才返回
if (ret == -1) { //錯誤情況
if (errno == EINTR) {//signal interrupt
continue;
}
else {
perror("select error");
exit(0);
}
}
else if (ret) { //返回值大於0 有資料到來
if (FD_ISSET(tcp_fd, &readfd))
{
if((connfd = accept(tcp_fd, (struct sockaddr *)&client_addr, &client_addr_len)) == -1) {
printf("accept socket error: %s(errno: %d)\n",strerror(errno),errno);
continue;
}
printf("accept a new connection from client IP: %s, port: %u\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
ret = init_new_client(connfd);
if (ret != 0) {
printf("init_new_client() failed ret = %d.\n", ret);
close(connfd);
}
} else if (FD_ISSET(udp_fd, &readfd)) {
int ret =recvfrom(udp_fd,recv_buff, MAXLINE - 1, 0,(struct sockaddr *)&client_addr, &client_addr_len);
if(ret<0){
perror("recvfrom error_1");
continue;
}
recv_buff[ret] = '\0';
printf("udp recv:");
printf("recv msg %d byte from client: %s\n", recv_len, recv_buff);
} else {
/*one fd can be read.*/
for (i = 0; i < MAX_FD_NUM; i++) {
if (client_fdset[i] != -1 && FD_ISSET(client_fdset[i], &readfd)) {
client_fd = client_fdset[i];
break;
}
}
memset(recv_buff, 0, MAXLINE);
recv_len = recv(client_fd, recv_buff, MAXLINE - 1, 0);
printf("tcp recv:");
printf("recv len = %d\n", recv_len);
if (recv_len <= 0) {
if (recv_len == 0) {
printf("socket disconnnect(or the other side shutdown socket fd, or network problem)\n");
printf("recv socket error: %s(errno: %d)\n",strerror(errno),errno);
}
else {
printf("recv socket error: %s(errno: %d)\n",strerror(errno),errno);
}
ret = remove_client(client_fd);
if (ret != 0) {
printf("remove_client() failed ret = %d.\n", ret);
}
close(client_fd);
continue;
}
recv_buff[recv_len] = '\0';
printf("recv msg %d byte from client: %s\n", recv_len, recv_buff);
if (send(client_fd, response, strlen(response), 0) < 0) {
printf("send socket error: %s(errno: %d)\n", strerror(errno), errno);
ret = remove_client(client_fd);
if (ret != 0) {
printf("remove_client() failed ret = %d.\n", ret);
}
close(client_fd);
continue;
}
}
}
else //ret 為0,超時情況
{
printf("time out\n");
//close(keybd_fd);//產生異常,檢視結果
}
}
close(tcp_fd);
close(udp_fd);
printf("--------server exit------.\n");
exit(0);
}
static int init_new_client(int client_fd)
{
int i;
for(i = 0; i < MAX_FD_NUM; i++) {
if (client_fdset[i] == -1) {
client_fdset[i] = client_fd;
break;
}
}
if (i == MAX_FD_NUM) {
printf("too many client.\n");
return -1;
}
return 0;
}
static int remove_client(int client_fd)
{
int i;
for(i = 0; i < MAX_FD_NUM; i++) {
if (client_fdset[i] == client_fd) {
client_fdset[i] = -1;
break;
}
}
if (i == MAX_FD_NUM) {
printf("client fd is not in list.\n");
return -1;
}
return 0;
}
static int get_max_fd(int fd)
{
int i;
for(i = 0; i < MAX_FD_NUM; i++) {
if (client_fdset[i] > fd) {
fd = client_fdset[i];
}
}
return fd;
}
client
tcp client 程式碼如下:
/*
TCP
client
API
usage:
./client 192.168.1.78
notes:
client 端connect()成功之後,此時立刻關閉sockfd斷開連線,
server 端recv()會立刻返回,其返回值為0;
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define SERVER_PORT 8888
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[MAXLINE], sendline[MAXLINE];
struct sockaddr_in servaddr;
int recv_len;
int ret;
memset(recvline, 0, MAXLINE);
memset(sendline, 0, MAXLINE);
if (argc != 2) {
printf("usage: ./client <ipaddress>\n");
exit(0);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
printf("inet_pton error for %s/n",argv[1]);
close(sockfd);
exit(0);
}
if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
printf("connect error: %s(errno: %d)/n",strerror(errno),errno);
close(sockfd);
exit(0);
}
/*sleep(10);close(sockfd);exit(0);*/
printf("send msg to server: \n");
fgets(sendline, MAXLINE - 1, stdin);
ret = send(sockfd, sendline, strlen(sendline), 0);
printf("the len of send data = %d.\n", ret);
if (ret < 0)
{
printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
close(sockfd);
exit(0);
} else if (ret == 0) {//return 0
printf("socket disconnnect(or the other side shutdown socket fd, or network problem) and send error: %s(errno: %d)\n",
strerror(errno), errno);
close(sockfd);
exit(0);
}
recv_len = recv(sockfd, recvline, MAXLINE - 1, 0);
if (recv_len < 0) {
printf("recv socket error: %s(errno: %d)\n",strerror(errno),errno);
close(sockfd);
exit(0);
} else if (recv_len == 0) {//return 0
printf("socket disconnnect(or the other side shutdown socket fd, or network problem) and recv error: %s(errno: %d)\n",
strerror(errno), errno);
close(sockfd);
exit(0);
}
recvline[recv_len] = '\0';
printf("from server: %s\n", recvline);
close(sockfd);
exit(0);
}
udp client 程式碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<errno.h>
#include<sys/types.h>
int port=8888;
int main(int argc,char *argv[]){
int sockfd;
int i=0;
int z;
char buf[80],str1[80];
struct hostent *host;
struct sockaddr_in adr_srvr;
if(argc<2){
fprintf(stderr,"please enter the server's hostname!\n");
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL){
herror("gethostbyname error!");
exit(1);
}
adr_srvr.sin_family=AF_INET;
adr_srvr.sin_port=htons(port);
adr_srvr.sin_addr=*((struct in_addr *)host->h_addr);
bzero(&(adr_srvr.sin_zero),8);
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd==-1){
perror("socket error!");
exit(1);
}
printf("msg to server:\n");
fgets(buf, sizeof(buf) - 1, stdin);
printf("send ....\n");
z=sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&adr_srvr,sizeof(adr_srvr));
if(z<0){
perror("sendto error");
exit(1);
}
sprintf(buf,"stop\n");
z=sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&adr_srvr,sizeof(adr_srvr));
if(z<0){
perror("sendto error");
exit(1);
}
close(sockfd);
exit(0);
}
測試:
編譯
$ gcc server_tcp_udp.c -o server_tcp_udp.c
$ gcc tcp_client.c -o tcp_client.c
$ gcc udp_client.c -o udp_client.c
啟動server
$ ./server_tcp_udp
$ ./server_tcp_udp
----------waiting for client's request---------
accept a new connection from client IP: 127.0.0.1, port: 60858
tcp recv:recv len = 30
recv msg 30 byte from client: Hello Server, I'm tcp client.
udp recv:recv msg 0 byte from client: Hello server, I'm udp client.
udp recv:recv msg 0 byte from client: stop
啟動tcp udp client
$ ./client_tcp 127.0.0.1
send msg to server:
Hello Server, I'm tcp client.
the len of send data = 30.
from server: recv well done.
$ ./client_udp 127.0.0.1
msg to server:
Hello server, I'm udp client.
send ....
可以看到,server端接到tcp client和udp client傳送的請求。