文章目录
1、整个流程图
图片来自《高质量嵌入式linux c编程第2版 第355页》
2、创建:socket()
- socket()用于创建一个通信的端口,并返回指向该端口的文件描述符。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain :指定地址类型
AF_INET :ipv4
AF_INET6 :ipv6- type:指定通信类型
SOCK_STREAM:提供双向连续且可信赖的数据流一般用于tcp通信
SOCK_DGRAM:提供不可信不连续的数据包连接一般用于udp通信- protocol:指定所使用协议的编号,一般为0
- 成功:
返回文件描述符,并且是当前进程未打开的编号最低的文件描述符。- 失败:
-1
socket参数的可选值还有很多,这里只选取tcp和udp常用的
- 示例
// ipv4 tcp
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1){
perror("socket");
exit(-1);
}
3、公布地址:bind()
- bind()为套接字分配名称,公布自己是谁
- 将addr指定的地址分配给文件描述符sockfd引用的套接字。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- sockfd:socket返回的文件描述符
- addr:详见:sockaddr和sockaddr_in
- addrlen:一般是sizeof(struct sockaddr)
- 成功:返回0
- 失败:返回 -1
- 示例
int ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr));//格式化
//bzero(&addr,sizeof(struct sockaddr))
addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(2233);//如果端口为0,会自动选择应该未被占用的
addr.sin_addr.s_addr = inet_addr("192.168.200.100");//将地址转为二进制
//addr.sin_addr.s_addr = inet_addr(INADDR_ANY);//自动填充为本机地址
ret = bind(sock_fd,(struct sockaddr *)&addr, sizeof(struct sockaddr);
if( ret == -1){
perror("bind error");
close(sock_fd);
exit(-1);
}
转换函数详见:网络字节序与主机字节序的转化、ipv4地址与二进制数字的转化
4、设置监听:listen()
- listen()将socket设置为监听模式,等待连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- sockfd:套接字
- backlog:能同时处理的最大连接请求
如果连接请求已满,客户端可能会收到ECONNREFUSED的错误
如果底层协议支持重传,则可能会忽略该请求,以便稍后在连接成功时重新尝试。
成功返回0 失败返回-1
- 示例
if(listen(sock_fd,5) == -1){
perror("listen error");
close(sock_fd);
exit(-1);
}
5、接受请求:accept()
- accept()用于接受连接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:套接字
- addr:用于保存请求者的地址数据
详见:sockaddr和sockaddr_in- addrlen:一般是保存 sizeof(struct sockaddr)变量的地址
成功返回新的套接字 失败返回-1
- 示例
int new_fd = 0;
struct sockaddr_in addr;
unsigned int addrlen = sizeof(struct sockaddr);
memset(&addr,0, sizeof(struct sockaddr_in));
new_fd = accept(sock_fd, (struct sockaddr *)&addr, &addrlen);
//new_fd = accept(sock_fd, NULL, NULL);//不考虑请求方的信息
if(new_fd == -1){
perror("accept error");
close(sock_fd);
exit(-1);
}
//打印请求者的地址和端口
printf("%s %d connect\n", inet_ntoa(addr.sin_addr),
ntohs(addr.sin_port));
6、请求连接:connect()
- connect()用于客户端向服务器发起连接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- sockfd:套接字
- addr:传入主机的地址信息
详见:sockaddr和sockaddr_in- addrlen:一般是 sizeof(struct sockaddr)
成功返回0 失败返回-1
- 示例
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1){
perror("socket error");
exit(-1);
}
int ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(2233);//主机端口
addr.sin_addr.s_addr = inet_addr("192.168.200.100");//主机地址
ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
if(ret == -1){
perror("connect error");
close(sock_fd);
exit(-1) ;
}
7、数据发送:send(),sendto()
- send一般用于tcp,sendto一般用于udp
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
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:一般为0
- dest_addr:接收方的端口信息
- addrlen:sizeof(struct sockaddr)
- 成功:
返回实际发送的字节数- 失败:
返回-1
- 示例
/* send */
if(send(sock_fd,"hello",6,0) == -1){
perror("send");
return -1;
}
/* sendto */
int ret = 0;
struct sockaddr_in dest;
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0)
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(2233);
dest.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = sendto(sock_fd, "hello", 5, 0,
(struct sockaddr *)&dest, sizeof(dest));
if(ret == -1){
perror("sendto");
return -1;
}
8、数据接收:recv(),recvfrom()
- recv一般用于tcp,recvfrom一般用于udp
- 若没收到消息则会阻塞,除非套接字是非阻塞的
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:套接字
- buf:用于保存接收的数据,可以为结构体类型
- len:要接收数据的长度
- flags:一般为0
- dest_addr:发送方的端口信息
- addrlen:sizeof(struct sockaddr)
- 成功:
返回实际接收的字节数- 失败:
返回-1
- 示例
/* recv */
char buf[2048];
int ret;
memset(buf, 0, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
if(ret == -1){
perror("recv error")
return -1;
}
buf[ret] = 0;//防止打印乱码
puts(buf);
/* recvfrom */
int num;
char buf[1024];
struct sockaddr_in remote;
num = recvfrom(sock_fd, buf, sizeof(buf)-1, 0,
(struct sockaddr *)&remote, &len);
if (num == -1) {
perror("recv from");
break;
}
9、tcp通信实例
tcp_fun.h
#ifndef TCP_FUN_H
#define TCP_FUN_H
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int tcp_server_init(const char *ip,int port);//服务器初始化
int tcp_accept(int sock_fd);//服务器接受请求
void tcp_server_chat(int new_fd);//服务器消息传输
int tcp_client_init(const char *ip,int port);//客户端初始化
void tcp_client_chat(int sock_fd);//服务器消息传输
#endif
tcp_fun.c
#include "tcp_fun.h"
/* 有几个函数不应该用exit(-1) 这个会让程序直接结束,但是懒得改了 */
int tcp_server_init(const char *ip,int port){
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1){
perror("socket error");
exit(-1);
}
// 配置监听描述符地址复用属性
//可不配置,主要涉及到4次挥手
int opt = 1;
int ret = setsockopt(sock_fd,SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
if (ret < 0) {
perror("set socket opt error");
exit(-1);
}
ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr_in));
//bzero(&addr,sizeof(struct sockaddr_in))
addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(port);//端口
addr.sin_addr.s_addr = inet_addr(ip);
//addr.sin_addr.s_addr = INADDR_ANY;//自动填充为本机地址
ret = bind(sock_fd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
if( ret == -1){
perror("bind error");
close(sock_fd);
exit(-1);
}
if(listen(sock_fd,5) == -1){
perror("listen error");
close(sock_fd);
exit(-1);
}
return sock_fd;
}
int tcp_accept(int sock_fd){
int new_fd = 0;
struct sockaddr_in addr;
unsigned int addrlen = sizeof(struct sockaddr);
memset(&addr,0, sizeof(struct sockaddr_in));
new_fd = accept(sock_fd, (struct sockaddr *)&addr, &addrlen);
//new_fd = accept(sock_fd, NULL, NULL);//不考虑请求方的信息
if(new_fd == -1){
perror("accept error");
close(sock_fd);
exit(-1);
}
//打印请求者的地址和端口
printf("%s %d connect\n", inet_ntoa(addr.sin_addr),
ntohs(addr.sin_port));
return new_fd;
}
void tcp_server_chat(int new_fd){
char buf[2048];
unsigned int ret;
// 循环接收,接收一条聊天信息
memset(buf, 0, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
while (ret) {
if (ret < 0) {
perror("recv error");
break;
}
buf[ret] = 0; // 由于是消息,所以对空间做尾部处理
printf("receive msg is %s.\n", buf);
/* //服务器也可以发消息给客户端
ret = send(new_fd, "hello", 5, 0);
if (ret < 0) {
perror("send");
break;
}
*/
// 再次等待接收客户端的消息
memset(buf, 0, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
}
printf("client close!\n");
close(new_fd);
}
int tcp_client_init(const char *ip,int port){
int sock_fd = socket(AF_INET,SOCK_STREAM,0);
if(sock_fd == -1){
perror("socket error");
exit(-1);
}
int ret = 0;
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;//ipv4
addr.sin_port = htons(port);//主机端口
addr.sin_addr.s_addr = inet_addr(ip);//主机地址
ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
if(ret == -1){
perror("connect error");
close(sock_fd);
exit(-1) ;
}
return sock_fd;
}
void tcp_client_chat(int sock_fd){
char buf[1024];
unsigned int ret;
printf("Client:");
while (fgets(buf, sizeof(buf), stdin)) {
// 发送字符串,不发送'\0'数据
ret = send(sock_fd, buf, strlen(buf) - 1, 0);
if (ret < 0) {
perror("send");
break;
}
printf("send %d bytes success!\n", ret);
printf("Client:");
}
}
server.c
#include "tcp_fun.h"
#define IP "192.168.200.134"//本机地址
#define PORT 2233
int main() {
int sock_fd = tcp_server_init(IP,PORT);
int new_fd;
printf("listening...\n");
while (1) {
new_fd = tcp_accept(sock_fd);
if (new_fd < 0)
break;
tcp_server_chat(new_fd);
}
}
client.c
#include "tcp_fun.h"
#define IP "192.168.200.134"
#define PORT 2233
int main() {
int sock_fd = tcp_client_init(IP,PORT);
tcp_client_chat(sock_fd);
}
Makefile
all:
gcc -o client client.c tcp_fun.c
gcc -o server server.c tcp_fun.c
10、udp通信实例
server.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#define IP "192.168.200.134"
#define PORT 2233
int main() {
int fd,ret;
unsigned int len;
char buf[1024] = {0};
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0){
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr,0, sizeof(struct sockaddr));
//公布自己的地址和端口
addr.sin_port = htons(PORT);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(IP);
ret = bind(fd,(struct sockaddr *)&addr, sizeof(struct sockaddr));
if(ret < 0){
perror("bind");
return -1;
}
printf("host :ip = %s port = %d\n",IP,PORT);
struct sockaddr_in c_addr;//暂存客户端的地址
len = sizeof(struct sockaddr);
memset(&c_addr,0, len);
ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&c_addr,&len);
while (ret>0){
//接收消息并回复
printf("recv %d bytes from %s:%d msg is: %s",ret,
inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port),buf);
sendto(fd,"msg recv",9,0,(struct sockaddr *)&c_addr,len);
//重复接收
memset(&c_addr,0, len);
ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&c_addr,&len);
}
return 0;
}
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <wait.h>
int main(int argc ,char **argv) {
int fd,ret,id;
unsigned int len;
char buf[1024];
if(argc < 3){
printf("参数过少\n");
return 0;
}
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0){
perror("socket");
return -1;
}
struct sockaddr_in s_addr;
len = sizeof(struct sockaddr);
memset(&s_addr,0, len);
s_addr.sin_port = htons(atoi(argv[1]));
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = inet_addr(argv[2]);
ret = 1;
id = fork();
if(id == 0){
//子进程发消息
while (ret > 0){
fgets(buf, sizeof(buf),stdin);
ret = (int)sendto(fd,buf, strlen(buf),0,(struct sockaddr *)&s_addr,len);
}
} else{
//父进程收消息(就不做出错判断)
ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&s_addr,&len);
while (ret>0){
//接收消息
printf("recv %d bytes from %s:%d msg is: %s\n",ret,
inet_ntoa(s_addr.sin_addr), ntohs(s_addr.sin_port),buf);
//重复接收
memset(&s_addr,0, len);
ret = (int)recvfrom(fd,buf,1024-1,0,(struct sockaddr *)&s_addr,&len);
}
wait(NULL);
}
return 0;
}
> 参考:man手册、《高质量嵌入式linuxc编程 第2版》355-363