一、阻塞与非阻塞select
Linux c socket编程中,bind()用于绑定IP和端口,listen()监听连接端口,如果没有连接到来,那么accept()会一直阻塞,直到有数据进来,但是如果我们需要有多个客户端访问服务端那就麻烦了,我们想要能够一边处理accept()到的请求,同时也能等待或accept()其它请求。
目前解决阻塞的方式有多进程、多线程、fcntl设置非阻塞模式、多路同步I/O select。
非阻塞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多客户端长连接
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编程实例
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下多线程如何同步
使用互斥锁、条件变量和信号量