Linux系统与网络编程13:Socket多线程

Posted hello world 999

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统与网络编程13:Socket多线程相关的知识,希望对你有一定的参考价值。

Socket多线程


OVERVIEW

fix1

将Socket多进程实现的聊天程序修改为多线程实现:

  1. 取消fork创建子进程操作
  2. 将线程的工作写在worker函数中,
  3. 创建让多个线程替代子进程传入worker函数去进行recv操作
void *worker(void *arg) 
	int sockfd = *(int *)arg;
	while (1) 
		char buff[1024] = 0;
		ssize_t rsize = recv(sockfd, buff, sizeof(buff), 0);//三次握手四次挥手
		if (rsize > 0) 
			printf("<receive> %s\\n", buff);
		 else 
			close(sockfd);
			break;
		
	
	printf("<server> : a client has left!\\n");
	//exit(0);某个线程exit则其他线程都会被exit

//3.accept循环的接受客户端对server的连接
while (1) 
    int newfd;//新建的文件描述符
    struct sockaddr_in client;//用于存放临时建立连接的客户端的信息
    socklen_t len = sizeof(client);
    if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");
    printf("<accept> %s:%d: accept a client!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    //4.多线程recv接受消息
    pthread_t tid;
    pthread_create(&tid, NULL, worker, (void *)&newfd);

10个客户端全部成功连接上服务器,

但是当客户端发送消息时,并不是所有客户端都得到了响应!线程之间发生竞争,导致部分client发送的消息无法得到响应!

可以发现发送hello2、hello3消息的客户端没有得到任何响应,后续在这两个客户端中发送消息也得不到任何响应。

fix2

为解决线程对文件描述符的竞争问题(进程中多个线程共享空间),创建文件描述符数组、创建线程数组解决线程竞争问题

//3.accept循环的接受客户端对server的连接
pthread_t tid[MAXUSER + 5];//线程tid数组 用于解决线程竞争
int fd[MAXUSER + 5];//文件描述符数组 用于解决文件描述符竞争
while (1) 
    int newfd;//新建的文件描述符
    struct sockaddr_in client;//用于存放临时建立连接的客户端的信息
    socklen_t len = sizeof(client);
    if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");
    printf("<accept> %s:%d: accept a client!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    //4.多线程recv接受消息
    fd[newfd] = newfd;//这样所使用的文件描述符 就是当前进程所打开的文件描述符中最小、未被使用的fd
    pthread_create(&tid[newfd], NULL, worker, (void *)&fd[newfd]);//成功解决tid与fd竞争的问题

在创建结构体数组、创建线程数组之后,所有客户端发送的消息都得到了响应,线程竞争问题解决。

fix3

注意到,server中的端口号和ip没有进行显示,这是为了简化问题在worker函数中暂时将客户端ip与端口不显示(简化参数传递过程)。

创建线程时执行work函数涉及传参问题:需使用结构体将参数封装后,将结构体指针传入,才能获取到ip地址与端口号。

  1. 对worker函数进行修改,恢复ip地址与端口号的显示(对传入的结构体参数进行解封)
  2. 修改pthread_create线程创建中参数传递的过程,将传递的newfd参数改为传递结构体参数(同时包含newfd与client信息)
void *worker(void *arg) 
	struct Data data = *(struct Data *)arg;//进行参数的类型转换
	int sockfd = data.fd;
	struct sockaddr_in client = data.client;//将参数进行解封装
	while (1) 
		char buff[1024] = 0;
		ssize_t rsize = recv(sockfd, buff, sizeof(buff), 0);//三次握手四次挥手
		if (rsize > 0) 
			printf("<receive> %s:%d: %s\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), buff);
		 else 
			close(sockfd);
			break;
		
	
	printf("<server> : %s:%d has left!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	//exit(0);某个线程exit则其他线程都会被exit

struct Data 
	int fd;
	struct sockaddr_in client;
;
//3.accept循环的接受客户端对server的连接
pthread_t tid[MAXUSER + 5];//线程tid数组 用于解决线程竞争
struct Data data[MAXUSER + 5];//文件描述符与client的结构体数组 用于解决文件描述符竞争
while (1) 
    int newfd;//新建的文件描述符
    struct sockaddr_in client;//用于存放临时建立连接的客户端的信息
    socklen_t len = sizeof(client);
    if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");
    printf("<accept> %s:%d: accept a client!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

    //4.多线程recv接受消息
    data[newfd].client = client;
    data[newfd].fd = newfd;//这样所使用的文件描述符 就是当前进程所打开的文件描述符中最小、未被使用的fd
    //创建线程进行work工作涉及传参问题:需要使用结构体将参数封装后,将结构体指针传给线程
    pthread_create(&tid[newfd], NULL, worker, (void *)&data[newfd]);//成功解决tid与fd竞争的问题

10个客户端都成功连接到服务器,并且所有客户端发送的消息都被server成功响应,并且每个客户端的ip地址与port端口号都正常显示。

socket多线程的修改完成。

sourcecode

以下是最终代码:

//server.c
#include "head.h"
#include "common.h"

#define MAXUSER 100

#define handle_error(msg) \\
	do  perror(msg); exit(EXIT_FAILURE);  while (0)

struct Data 
	int fd;
	struct sockaddr_in client;
;

void *worker(void *arg) 
	struct Data data = *(struct Data *)arg;//进行参数的类型转换
	int sockfd = data.fd;
	struct sockaddr_in client = data.client;//将参数进行解封装
	while (1) 
		char buff[1024] = 0;
		ssize_t rsize = recv(sockfd, buff, sizeof(buff), 0);//三次握手四次挥手
		if (rsize > 0) 
			printf("<receive> %s:%d: %s\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), buff);
		 else 
			close(sockfd);
			break;
		
	
	printf("<server> : %s:%d has left!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	//exit(0);某个线程exit则其他线程都会被exit


int main(int argc, char *argv[]) 
	//./a.out -p port
	//1.命令行解析
	if (argc != 3) 
		fprintf(stderr, "Usage : %s -p port", argv[0]);
		exit(1);
	
	int opt;
	int port;
	int sockfd;
	while ((opt = getopt(argc, argv, "p:")) != -1) 
		switch (opt) 
			case 'p':
				port = atoi(optarg);
				break;
			default:
				fprintf(stderr, "Usage : %s -p port\\n", argv[0]);
				exit(1);
		
	
	//2.创建socket
	if ((sockfd = socketCreate(port)) < 0) handle_error("socketCreate");
	
	//3.accept循环的接受客户端对server的连接
	pthread_t tid[MAXUSER + 5];//线程tid数组 用于解决线程竞争
	struct Data data[MAXUSER + 5];//文件描述符与client的结构体数组 用于解决文件描述符竞争
	while (1) 
		int newfd;//新建的文件描述符
		struct sockaddr_in client;//用于存放临时建立连接的客户端的信息
		socklen_t len = sizeof(client);
		if ((newfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0) handle_error("accept");
		printf("<accept> %s:%d: accept a client!\\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

		//4.多线程recv接受消息
		data[newfd].client = client;
		data[newfd].fd = newfd;//这样所使用的文件描述符 就是当前进程所打开的文件描述符中最小、未被使用的fd
		//创建线程进行work工作涉及传参问题:需要使用结构体将参数封装后,将结构体指针传给线程
		pthread_create(&tid[newfd], NULL, worker, (void *)&data[newfd]);//成功解决tid与fd竞争的问题
	
	return 0;

//client.c
#include "head.h"
#include "common.h"

#define handle_error(msg) \\
	do  perror(msg); exit(EXIT_FAILURE);  while (0)

int sockfd;

//ctrl+c信号处理
void closeSock(int signum) 
	send(sockfd, "I am leaving...", 27, 0);
	close(sockfd);//关闭客户端文件描述符
	exit(0);


int main(int argc, char *argv[]) 
	//./a.out ip port
	if (argc != 3) 
		fprintf(stderr, "Usage : %s ip port\\n", argv[0]);
		exit(1);
	
	signal(SIGINT, closeSock);
	//1.建立连接connect
	if ((sockfd = socketConnect(argv[1], atoi(argv[2]))) < 0) handle_error("socketConnect");
	printf("connect sccuess!\\n");

	//2.send发送数据
	while (1) //循环发送消息
		char buff[1024] = 0;
		scanf("%[^\\n]s", buff);//输入可含空格的字符串
		getchar();//吞掉一个换行
		//只要向文件描述符中写入 tcp服务就会帮助发送消息
		//With a zero flags argument, send is equivalent to write(2).
		if (strlen(buff)) send(sockfd, buff, sizeof(buff), 0);
	
	return 0;

//common.h
#ifndef _COMMON_H
#define _COMMON_H

int socketCreate(int port);
int socketConnect(const char *ip, int port);

#endif
//common.c
#include "head.h"

int socketCreate(int port) 
	//1.创建套接字
	int sockfd;
	struct sockaddr_in addr;
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);//主机字节序转换为网络字节序
	addr.sin_addr.s_addr = inet_addr("0.0.0.0");//将网络字节序转化为主机字节序 0.0.0.0表示不关注消息传来的地址

	//2.bind绑定套接字与结构体信息 listen
	if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0) return -1;
	if (listen(sockfd, 20) < 0) return -1;
	return sockfd;


int socketConnect(const char *ip, int port) 
	int sockfd;
	//1.客户端打开一个socket
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;

	//2.定义结构体用于绑定端口号、ip地址(存放服务端的具体信息)
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);//端口号
	server.sin_addr.s_addr = inet_addr(ip);//ip地址

	//3.建立连接connection
	if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) return -1;
	return sockfd;

Liunx C 编程之多线程与Socket

多线程

pthread.h是linux特有的头文件,POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32。

创建线程
1.pthread_create 创建一个新线程并使之运行起来。该函数可以在程序的任何地方调用包括线程内,线程是没有依赖关系的。
2.一个进程可以创建的线程最大数量取决于系统实现
3. pthread_create参数:
        thread:返回一个不透明的,唯一的新线程标识符。
        attr:不透明的线程属性对象。可以指定一个线程属性对象,或者NULL为缺省值。
        start_routine:线程将会执行一次的C函数。
        arg: 传递给start_routine单个参数,传递时必须转换成指向void的指针类型。没有参数传递时,可设置为NULL。

pthread_create (threadid,attr,start_routine,arg)

结束线程
1.结束线程的方法有一下几种:
       线程从主线程(main函数的初始线程)返回。
       线程调用了pthread_exit函数。
       其它线程使用 pthread_cancel函数结束线程。
       调用exec或者exit函数,整个进程结束。

2.如果main()在其他线程创建前用pthread_exit()退出了,其他线程将会继续执行。否则,他们会随着main的结束而终止。

pthread_exit (status)
int pthread_cancel(pthread_t threadid);

等待线程状态

pthread_join (threadid,status)  

例子:

 1 #include <stdio.h>
 2 #include <pthread.h> //liunx线程头文件
 3 #include <stdlib.h>
 4 //线程
 5 void *thread1_proc(void *arg)
 6 
 7     int i=*(int *)arg;    //取出内容 
 8     free(arg);//释放空间 
 9     while(i<105)
10     
11         printf("thread1:%-5d",i);
12         sleep(2);//延时等待两秒 
13         i++;
14     
15     printf("Thread1 finished!\\n");
16     pthread_exit(NULL);//终止当前线程
17 
18 void main()
19 
20     pthread_t thread1;
21     int *ixi=(int *)malloc(sizeof(int));//在堆中申请一块内容 
22     *ixi=100; //存在内容 
23     if(pthread_create(&thread1,NULL,thread1_proc,(void *)ixi)!=0)//创建线程1并传递参数 
24         perror("Create thread failed:");//创建错误时执行
25     //终止当前线程,此时会子线程会执行完毕,相当于在此处join所有子线程一样 
26     pthread_exit(NULL);//(1)结束主
27     // pthread_join(thread1,NULL);//(2)可替换上一条
28     printf("主线程已经退出,本条不执行"); //(1)不执行,(2)执行该条
29 

多线程共享资源

共享资源时可能会出现操作未完成而被另一个线程打破,造成资源存取异常

定义变量

#include <pthread.h> 
pthread_mutex_t lockx;

初始化

pthread_mutex_init(&lockx,NULL);

上锁与解锁

pthread_mutex_lock(&lockx);//上锁 
//独立资源
//代码块 
pthread_mutex_unlock(&lockx);//解锁

信号量
实现循序控制
定义变量

#include <semaphore.h>
sem_t can_scanf;

初始化

sem_init(&can_scanf,0,1);

PV操作

sem_wait(&can_scanf);//等待信号量置位并进行减一操作
sem_post(&can_scanf); //信号量加一 操作

例子
主线程负责从键盘获取两个整数,子线程1负责对这两个整数完成求和运算并把结果打印出来,子线程2负责对这两个整数完成乘法运算并打印出来。三个线程要求遵循如下同步顺序:
1.主线程获取两个数;
2.子线程1计算;
3.子线程2计算;
4.转(1)

 1 #include <stdio.h>
 2 #include <semaphore.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 sem_t can_add;//能够进行加法计算的信号量
 6 sem_t can_mul;//能够进行输入的信号量 
 7 sem_t can_scanf;//能够进行乘法计算的信号量
 8 int x,y;
 9 void *thread_add(void *arg)//加法线程入口函数
10 
11     while(1)
12     
13         sem_wait(&can_add);
14         printf("%d+%d=%d\\n",x,y,x+y);
15         sem_post(&can_mul);
16     
17 
18 void *thread_mul(void *arg)//乘法线程入口函数
19 
20     while(1)
21     
22         sem_wait(&can_mul);
23         printf("%d*%d=%d\\n",x,y,x*y);
24         sem_post(&can_scanf);
25     
26 
27 int main()
28 
29     pthread_t tid;
30     int arg[2];
31     //信号量初始化 
32     sem_init(&can_scanf,0,1);
33     sem_init(&can_add,0,0);
34     sem_init(&can_mul,0,0);
35     if(pthread_create(&tid,NULL,thread_add,NULL)<0)
36     
37         printf("Create thread_add failed!\\n");
38         exit(0);
39     
40     if(pthread_create(&tid,NULL,thread_mul,NULL)<0)
41     
42         printf("Create thread_mul failed!\\n");
43         exit(0);
44     
45     while(1)
46     
47         sem_wait(&can_scanf);//等待信号量置位并进行减一操作 
48         printf("Please input two integers:");
49         scanf("%d%d",&x,&y);
50         sem_post(&can_add);//信号量加一 操作 
51             
52 

Socket编程

数据包的发送
(1)TCP(write/send)
基于流的,没有信息边界,所以发送的包的大小没有限制;但由于没有信息边界,就得要求要求应用程序自己能够把逻辑上的包分割出来。
(2)UDP(sendto)
基于包的,应用层的包在由下层包的传输过程中因尽量避免有分片——重组的发生,否则丢包的概率会很大,在以太网中,MTU的大小为46——1500字节,除掉IP层头、udp头,应用层的UDP包不要超过1472字节。鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时。 最好将UDP的数据长度控制在548字节(576-8-20)以内.
数据包的接收
(1)TCP(read/recv)
如果协议栈缓冲区实际收到的字节数大于所请求的字节数,则返回实际要读取的字节数,剩余未读取的字节数下次再读;
如果协议栈缓冲区实际收到的字节数小于所请求的字节数,则返回所能提供的字节数;
(2)UDP(recvfrom)
如果协议栈缓冲区实际收到的字节数大于所请求的字节数,在linux下会对数据报进行截段,并丢弃剩下的数据;
如果协议栈缓冲区实际收到的字节数小于所请求的字节数,则返回所能提供的字节数;
注意点
当发送函数返回时,并不表示数据包已经到达了目标计算机,仅仅说明待发送的数据包被协议栈给接收了;
UDP的数据包要么被接收,要么丢失;TCP的数据报一定会无差错按序交付给对方

TCP

服务端
1、连接WiFi或者开启AP,使模块接入网络
2、socket 创建一个套接字
socket可以认为是应用程序和网络之间信息传输通道,所以TCP编程服务端、客户端的第一步就是要建立这个信息传输的通道,主要通过socket函数完成。

技术图片

3、 Bind socket信息
给在第一步中所创建的socket显式指定其ip地址和端口号(bind)
其中结构体为:

//设置server的详情信息
struct sockaddr_in server_addr,client_addr;
u32_t sock_size=sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(2351); //端口
//绑定本机的所有IP地址htonl(INADDR_ANY),确定某个inet_addr(“172.16.4.1”)
server_addr.sin_addr.s_addr =htonl(INADDR_ANY); 
bind(connect_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

技术图片

4、 listen确定请求队列的最大值

技术图片

5、 accept等待接入
此函数为所有网络函数中最难理解的一个函数,它的调用将意味着服务端开始处理外来请求,如果没有外来请求(也就是没有listen到请求进来)默认情况下则阻塞;当有外来请求时会新产生一个soket,并返回其描述符,应用程序将在这个新的socket上和请求者进行会话(读、写该socket),原套接字sockfd则继续侦听

技术图片

6、 send
当send返回时,并不是表示数据已经发送到了对方,而仅仅表示数据已经到了协议栈的缓冲区中。最后一个值在ESP32中不可用

 技术图片

7、 recv
默认情况下,当没有可接收的数据时则阻塞,参数len表示最多接收多少个字节数, 成功的接受的字节数完全可以小于len。最后一个值在ESP32中不可用

技术图片

客户端
1、连接WiFi或者开启AP,使模块接入网络
2、socket 创建一个套接字,参考服务器
3、是指向服务端发起连接请求(请求成功的前提是服务端已经进入了accept状态)
结构体参数

//设置server的详情信息
struct sockaddr_in server_addr,client_addr;
u32_t sock_size=sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(2351); //端口
//绑定本机的所有IP地址htonl(INADDR_ANY),确定某个inet_addr(“172.16.4.1”)
server_addr.sin_addr.s_addr = inet_addr("192.168.43.21"); 
int ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//连接服务器

技术图片

4、recv 和 send

 服务器示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #define MAXCONN 8
 9 int main()
10 
11     int listen_fd,comm_fd;
12     int ret;
13     int i=1;
14     struct sockaddr_in server_addr,client_addr;
15     int sock_size=sizeof(struct sockaddr_in);
16     listen_fd=socket(AF_INET,SOCK_STREAM,0);//创建一个socket,参数(IPV4,TCP,0)
17     if(listen_fd<0)
18     
19         perror("Failed to create socket:");
20         return -1;
21     
22     bzero(&server_addr,sock_size);//清零server_addr
23     server_addr.sin_family=AF_INET;//IPV4
24     server_addr.sin_port=htons(8000);//端口
25     server_addr.sin_addr.s_addr=INADDR_ANY;//绑定主机全部网络地址
26     setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));//设置套接字关联的选 项
27     ret=bind(listen_fd,(struct sockaddr*)&server_addr,sock_size);//网络主机绑定
28     if(ret==0)
29     
30         printf("Bind Successfully!\\n");
31     
32     ret=listen(listen_fd,MAXCONN);//确定最大监听数
33     if(ret==0)
34     
35         printf("Listen Successfully!\\n");
36     
37     while((comm_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sock_size))>=0)//阻塞并等待接入
38     
39         char ipaddr[16];
40         inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//网络地址符转换
41         printf("连接进入:%s\\n",ipaddr);
42         while(1)
43         
44             char buff[512];
45             int count;
46             count=read(comm_fd,buff,511);//读数据,接收
47             if(count>0)//判断接收的字节数是否大于零
48             
49                 buff[count]=0;//截断字符串
50                 printf("收到来自 %s 的数据:%s\\n",ipaddr,buff);
51                 if(strncmp(buff,"quit",4)==0)//判断退出条件
52                 
53                     printf("%s已经退出退出,等待下一个连接\\n",ipaddr);
54                     break;//退出此个连接,进行下一个连接接入
55                 
56                 write(comm_fd,buff,count);//写数据,发送
57             
58             else
59             
60                 printf("A talking is over!\\n");
61                 break;  //客户端断开                  
62             
63         
64     
65     close(listen_fd);//关闭连接
66     return 0;
67     
68 

客户端示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #include <string.h>
 9 int main(int argc,char **argv)
10 
11     int client_fd;
12     int ret;
13     int count;
14     struct sockaddr_in server_addr;
15     char buf[512];
16     char recv_buf[512];
17     int sock_size=sizeof(struct sockaddr_in);
18     if(argc<2)
19     
20         printf("Usage:./client serverip\\n");
21         return 0;
22     
23     bzero(&server_addr,sock_size);//清零server_addr
24     client_fd=socket(AF_INET,SOCK_STREAM,0);//创建一个socket,参数(IPV4,TCP,0)
25     server_addr.sin_family=AF_INET;
26     server_addr.sin_port=htons(8000);
27     server_addr.sin_addr.s_addr=inet_addr(argv[1]);
28     ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//连接服务器
29     if(ret<0)
30     
31         perror("Failed to connect:");
32         return -1;
33     
34     printf("Connect successfully!\\n");
35     while(1)
36         printf("请输入要发送的内容:");
37         fgets(buf,512,stdin);//从键盘获取字符串
38         ret=write(client_fd,buf,strlen(buf));//写数据,发送
39         if(ret<=0)
40             break;
41         if(strncmp(buf,"quit",4)==0)
42             printf("程序退出\\n");
43             break;    
44         
45         count=read(client_fd,recv_buf,511);//读数据,接收
46         if(count>0)
47         
48             recv_buf[count]=0;//截断接收的字符串
49             printf("Echo:%s\\n",recv_buf);
50             
51         else
52         
53             break;//服务器断开
54         
55     
56     close(client_fd);//关闭连接
57     return 0;
58     
59 

UDP

服务器
1、 创建socket
2、 调用函数设置udp播

int  setsockopt(int  s,  int level,  int  optname, const void *optval, socklen_t  optlen);
头文件:<sys/socket.h>
level : 选项级别(例如SOL_SOCKET)
optname : 选项名(例如SO_BROADCAST)
optval : 存放选项值的缓冲区的地址
optlen : 缓冲区长度
返回值:成功返回0   失败返回-1并设置errno

3、 绑定服务器信息bind
4、 数据收发
数据发送

int  sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen);
返回:大于0-成功发送数据长度;-1-出错;
UDP套接字使用无连接协议,因此必须使用sendto函数,指明目的地址;
msg:发送数据缓冲区的首地址;
len:缓冲区的长度;
flags:传输控制标志,通常为0;
to:发送目标;
tolen: 地址结构长度——sizeof(struct sockaddr)

数据接收

int  recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);
返回:大于0——成功接收数据长度;-1——出错;
buf:接收数据的保存地址;
len:接收的数据长度
flags:是传输控制标志,通常为0;
from:保存发送方的地址
fromlen: 地址结构长度。

服务器示例

 1 #include <sys/socket.h>
 2 #include <netinet/in.h>
 3 #include <arpa/inet.h>
 4 #include <string.h>
 5 #include <stdio.h>
 6 int main()
 7 
 8     int sockfd;
 9     int ret;
10     char buff[512];
11     char ipaddr[16];
12     struct sockaddr_in server_addr,client_addr;
13     int i=1;
14     int sock_size=sizeof(struct sockaddr_in);
15     sockfd=socket(AF_INET,SOCK_DGRAM,0);
16     if(sockfd<0)
17     
18         perror("Failed to socket:");
19         return -1;
20     
21     bzero(&server_addr,sock_size);
22     server_addr.sin_family=AF_INET;//服务器相关参数设置
23     server_addr.sin_port=htons(6000);
24     server_addr.sin_addr.s_addr=INADDR_ANY;
25     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));
26     if(bind(sockfd,(struct sockaddr*)&server_addr,sock_size)<0)//等待客户端接入,阻塞
27     
28         perror("Failed to bind:");
29         return -1;
30     
31     while(1)
32     
33         ret=recvfrom(sockfd,buff,512,0,(struct sockaddr*)&client_addr,&sock_size);//收到数据包
34         if(ret>0)
35         
36             buff[ret]=0;
37             inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//网络地址符转换
38             printf("Receive a string from %s:%d,data:%s\\n",ipaddr,client_addr.sin_port,buff);
39             if(strncmp(buff,"exit",4)==0)//退出
40               printf("Socket server exit ");
41               close(sockfd);//关闭socket    
42               break;    
43             
44             sendto(sockfd,buff,ret,0,(struct sockaddr*)&client_addr,sock_size);
45         
46     
47     close(sockfd);    
48 

客户端示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #include <strings.h>
 9 int main(int argc,char **argv)
10 
11     int client_fd;
12     int ret;
13     int count;
14     struct sockaddr_in server_addr,sock_addr;
15     char buf[512];
16     char recv_buf[512];
17     int sock_size=sizeof(struct sockaddr_in);
18     if(argc<2)
19     
20         printf("Usage:./udpclient serverip\\n");
21         return 0;
22     
23     client_fd=socket(AF_INET,SOCK_DGRAM,0);
24     bzero(&server_addr,sock_size);
25     server_addr.sin_family=AF_INET;
26     server_addr.sin_port=htons(6000);
27     server_addr.sin_addr.s_addr=inet_addr(argv[1]);
28     while(1)
29        
30         printf("In:");
31         fgets(buf,512,stdin);
32         ret=sendto(client_fd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sock_size);
33         if(ret<0)
34         
35             perror("Failed to sendto:");
36             break;
37         
38         if(strncmp(buf,"exit",4)==0)
39             break;    
40         count=recvfrom(client_fd,recv_buf,512,0,(struct sockaddr*)&sock_addr,&sock_size);
41         if(count>0)
42         
43             recv_buf[count]=0;
44             printf("Echo:%s\\n",recv_buf);
45             
46         else
47         
48             perror("Failed to recvfrom:");
49             break;
50         
51     
52     close(client_fd);
53     return 0;
54     
55 

参考:

https://www.cnblogs.com/mywolrd/archive/2009/02/05/1930707.html

物联网网关开发技术(罗老师)

以上是关于Linux系统与网络编程13:Socket多线程的主要内容,如果未能解决你的问题,请参考以下文章

廖雪峰Java13网络编程-1Socket编程-3TCP多线程编程

《Linux 应用编程》—第13章 Linux 多线程编程

Linux操作系统 - 网络编程socket

Linux下基于TCP协议的群聊系统设计(多线程+select)

Linux下的基于Pthread的多线程Socket编程

多线程编程以及socket编程_Linux程序设计4chapter15