socket TCP长连接和UDP网络编程实例、阻塞与非阻塞select、常见面试题

一、阻塞与非阻塞select

Linux c socket编程中,bind()用于绑定IP和端口,listen()监听连接端口,如果没有连接到来,那么accept()会一直阻塞,直到有数据进来,但是如果我们需要有多个客户端访问服务端那就麻烦了,我们想要能够一边处理accept()到的请求,同时也能等待或accept()其它请求。

目前解决阻塞的方式有多进程、多线程、fcntl设置非阻塞模式、多路同步I/O select。

Linux select I/O多工机制

非阻塞select的函数定义:select(int number, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout),其中:

number为最大的文件描述符加1;

readfds,writefds,exceptfds为描述符组,返回描述符的读、写、异常的情况。

描述符组的处理方式如下:

FD_CLR(int fd, fd_set *set); // 清除描述符组的fd位
FD_ISSET(int fd, fd_set *set); // 检查set中的fd位是否为真
FD_SET(int fd, fd_set *set); // 设置set中的fd位
FD_ZERO(fd_set *set); // 清除set的全部位。

timeval结构体的声明如下:

struct timeval{
	time_t tv_sec;
	time_t tv_usec;
}

本文主要使用多线程的方式解决阻塞,下面一起看看多线程的实现实例。

二、TCP多客户端长连接

socket TCP创建连接过程

1、线程相关操作函数

创建多线程的头文件为:<pthread.h>,常见的函数有:

(1)int pthread_create(pthread_t *restrict ptid, const pthread_attr_t
*restrict attr, void *(thread_run), void *restrict arg),

该函数用于创建一个线程,编译时要加上参数-lpthread,成功返回0,错误返回错误编号,其中

ptid为线程标识符;

attr为线程的属性;

thread_run实际的线程代码,一个函数指针;

arg为线程函数的参数

(2)int pthread_equal(pthread_t pt1, thread_t pt2);

比较两个线程的标识ID是否相等,返回0不相等,返回非0则相等。

(3)pthread_t pthread_self();

返回调用线程的标识ID。

2、创建socket TCP服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT 8080
#define IP "192.168.1.2"
#define MAX_SOCKET 4

struct html_request{
	char version[16];
	char protocol[16];
	char method[16];
	char content[256];
};

struct html_response{
	char version[16];
	char protocol[16];
	char content[256];
};

int init_tcp_socket(char *ip, int port){
	int socket_s = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_s < 0){
		perror("socket");
		exit(1);
	}
	struct sockaddr_in addr;
	bzero(&addr, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	if(bind(socket_s, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0){
		perror("socket bind");
		exit(1);
	}
	if(listen(socket_s, MAX_SOCKET) < 0){
		perror("socket listen");
		exit(1);
	}
	return socket_s;
}

void thread_run(void *arg){
	int socket_n = (int)arg;
	int length_rst = sizeof(struct html_request);
	int length_rep = sizeof(struct html_response);
	while(1){
		struct html_request request;
		memset(&request, 0, sizeof(length_rst));
      		if(recv(socket_n, &request, length_rst, 0) < 0){
			perror("socket recv");
			break;
		}
        	int time = get_time();
       		printf("%d      version: %s\n", time, request.version);
       		printf("%d      protocol: %s\n", time, request.protocol);
       		printf("%d      method: %s\n", time, request.method);
       		printf("%d      content: %s\n\n", time, request.content);
		
		struct html_response response = {"1.0", "HTTP Response", "thanks for visiting!"};
	//	response.version = 1.3;
	//	response.protocol = "HTTP";
	//	response.content = "thanks for visiting!";
		if(send(socket_n, &response, length_rep, 0) < 0){
			perror("socket send");
			break;
		}
	}
	close(socket_n);
}

int get_time(){
	int t = time(NULL);
	return t;
}

int main(int argc, char *argv[]){
	int socket_s = init_tcp_socket(IP, PORT);
	while(1){
		struct sockaddr_in c_addr;
		int len = sizeof(struct sockaddr);
		int socket_n = accept(socket_s, (struct sockaddr *)&c_addr, &len);
		if(socket_n < 0){
			perror("socket accept");
			continue;
		}
		pthread_t id;
		int code = pthread_create(&id, NULL, (void*)thread_run, (void*)socket_n);
		if(code){
			perror("thread create");
			exit(1);
		}
	} 
        return 0;
}

3、创建socket TCP客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_PORT 8080
#define SERVER_IP "192.168.1.2"

struct html_request{
        char version[16];
        char protocol[16];
        char method[16];
        char content[256];
};

struct html_response{
        char version[16];
        char protocol[16];
        char content[256];
};

int init_tcp_socket(char *ip, int port){
	int socket_s = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in addr;
	bzero(&addr, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	connect(socket_s, (struct sockaddr *)&addr, sizeof(struct sockaddr));
	return socket_s;	
}

void talk(int socket_s){
	int length_rst = sizeof(struct html_request);
	int length_rep = sizeof(struct html_response);
	while(1){
		char message[256];
		memset(message, 0 ,sizeof(message));
		read(0, message, sizeof(message));
		struct html_request request = {"1.7", "HTTP Request", "GET", ""};
		bzero(request.content, 256);
		strcpy(request.content, message);
		if(send(socket_s, &request, length_rst, 0) < 0){
			perror("socket send");
			return;
		}

		struct html_response response;
		if(recv(socket_s, &response, length_rep, 0) < 0){
			perror("socket recv");
			return;
		}
		int time = get_time();
		printf("%d	version: %s\n", time, response.version);
		printf("%d	protocol: %s\n", time, response.protocol);
		printf("%d	content: %s\n\n", time, response.content);
	}
	close(socket_s);
}

int get_time(){
        int t = time(NULL);
        return t;
}

int main(int argc, char *argv[]){
	int socket_s = init_tcp_socket(SERVER_IP, SERVER_PORT);
	talk(socket_s);
	return 0;
}

三、UDP编程实例

socket UDP客户端和服务端创建连接过程

1、创建UDP服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT 8080
#define IP "192.168.1.2"
#define MAX_SOCKET 4

int main(int argc, char *argv[]){
        int socket_s = socket(AF_INET, SOCK_DGRAM, 0);
        if(socket_s < 0){
                perror("socket");
                exit(1);
        }
        struct sockaddr_in addr, c_addr;
        int len = sizeof(struct sockaddr_in);
        bzero(&addr, len);
        bzero(&c_addr, len);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(PORT);
        addr.sin_addr.s_addr = inet_addr(IP);
        if(bind(socket_s, (struct sockaddr *)&addr, len) < 0){
                perror("socket bind");
                exit(1);
        }

        char buffer[256];
        while(1){
                bzero(buffer, 256);
                if(recvfrom(socket_s, &buffer, sizeof(buffer), 0, (struct sockaddr *)&c_addr, &len) < 0){
                        perror("socket recvfrom");
                        continue;
                }
                printf("message: %s\n", buffer);
                char response[256] = "server response.";
                if(sendto(socket_s, &response, sizeof(response), 0, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) < 0){
                        perror("socket sendto");
                        continue;
                }
        }
        close(socket_s);
        return 0;
}

2、创建UDP客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define IP "192.168.1.2"

int main(int argc, char *argv[]){
        int socket_s = socket(AF_INET, SOCK_DGRAM, 0);
        if(socket_s < 0){
                perror("socket");
                exit(1);
        }
        struct sockaddr_in addr;
        int len = sizeof(struct sockaddr_in);
        bzero(&addr, len);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(PORT);
        addr.sin_addr.s_addr = inet_addr(IP);

        while(1){
                char message[256];
                read(0, message, sizeof(message));
                if(sendto(socket_s, &message, sizeof(message), 0, (struct sockaddr *)&addr, len) < 0){
                        perror("socket sendto");
                        continue;
                 }
                char buffer[256];
                if(recvfrom(socket_s, &buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, &len) < 0){
                        perror("socket recvfrom");
                        continue;
                }
                printf("response: %s\n", buffer);
        }
        close(socket_s);
        return 0;
}

四、Linux网络编程常见面试题

1、 线程和进程的区别
线程属于进程,进程属于应用,线程基于栈编程,进程可单独编程。

2、 TCP和UDP的区别
TCP面向连接,建立连接器首先进行TCP三次握手,以确保连接的可靠性,不会丢失数据或乱序,提供重发机制。

3、 linux下多线程如何同步
使用互斥锁、条件变量和信号量

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?