Last Updated: 2023-04-25 09:51:26 Tuesday
-- TOC --
本文尝试总结在Linux下,用C语言进行socket编程的相关知识点。
struct sockaddr {
unsigned short sa_family; // 地址族,2字节
char sa_data[14]; // 存放地址和端口
}
struct in_addr {
in_addr_t s_addr; //in_addr_t为32位的unsigned int
};
struct sockaddr_in {
short int sin_family; // 地址族
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // 地址
// 8字节数组,全为0,
// 该字节数组的作用是为了让两种数据结构大小相同
unsigned char sin_zero[8];
}
struct sockaddr
和struct sockaddr_in
这两个结构体的size一样。
下面是UDP Server,将收到的消息打印出来:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == -1) _pee();
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
myaddr.sin_port = htons(56789);
if (bind(sock, (struct sockaddr*)&myaddr, sizeof(myaddr)) == -1)
_PEXIT;
struct sockaddr_in paddr;
socklen_t paddr_len = sizeof(paddr);
char rbuf[1024];
int v;
while (1) {
memset(rbuf, 0, 1024);
v = recvfrom(sock, rbuf, 1024, 0,(struct sockaddr*)&paddr, &paddr_len);
if (v == -1)
_PEXIT;
else if (v > 0)
printf("[recv from %s:%d] %s\n",
inet_ntoa(*(struct in_addr*)&paddr.sin_addr.s_addr),
ntohs(paddr.sin_port),
rbuf);
}
close(sock);
return 0;
}
下面是UDP Client,发送数据后退出:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == -1)
_PEXIT;
struct sockaddr_in paddr;
memset(&paddr, 0, sizeof(paddr));
paddr.sin_family = AF_INET;
paddr.sin_addr.s_addr = inet_addr("127.0.0.1");
paddr.sin_port = htons(56789);
socklen_t paddr_len = sizeof(paddr);
char sbuf[] = "Hello Socket!";
if (sendto(sock,
sbuf,
sizeof(sbuf),
0,
(struct sockaddr*)&paddr,
paddr_len) == -1)
_PEXIT;
close(sock);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <sys/select.h>
#include <fcntl.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
printf("Create socket failed.\n");
return 1;
}
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr("127.0.0.1");
dest.sin_port = htons(12345);
/*if (connect_with_timeout(sock, &dest, 2) == 1) {
printf("Connect timeout.\n");
return 1;
}*/
struct timeval timeo = {2,0};
if(setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,&timeo,sizeof(timeo)))
_PEXIT;
if (connect(sock,(struct sockaddr*)&dest,sizeof(dest)) < 0) {
if (errno != EINPROGRESS)
_PEXIT;
else {
printf("Connect timeout.\n");
return 1;
}
}
sleep(0.5);
char rbuf[64] = {0};
recv(sock, rbuf, 64, 0);
printf("recv: %s\n", rbuf);
char msg[] = "hello socket in c!";
printf("send: %s\n", msg);
send(sock, msg, strlen(msg), 0);
sleep(0.5);
memset(rbuf, 0, 64);
recv(sock, rbuf, 64, 0);
printf("recv: %s\n", rbuf);
close(sock);
return 0;
}
默认的connect超时时间太长了。connect接口的timeout,可以用setsockopt来设置。
send,sendto,sendmsg,connect
,适用SO_SNDTIMEO
选项;
recv,recvfrom,recvmsg,accept
,适用SO_RCVTIMEO
选项。
这段代码,没有考虑TCP socket发送接收数据的所谓粘包
问题,即send和recv只是返回成功的byte数,与接口的输入长度不一定一致。
/*
* connect a socket destination with timeout,
* the intput socket is in block mode, and also in block mode after return.
*/
int connect_with_timeout(int sock,
struct sockaddr_in *dest,
int timeout_in_second) {
int v;
/* save flags and set O_NONBLOCK */
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
/* initiate connect */
if ((v=connect(sock,(struct sockaddr*)dest,sizeof(*dest))) == 0) {
fcntl(sock, F_SETFL, flags);
return 0;
}
else if ((v<0) && (errno!=EINPROGRESS)) _pee();
/* wait by select */
struct timeval timeout = {timeout_in_second, 0};
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(sock, &read_set);
if ((v=select(sock+1, &read_set, NULL, NULL, &timeout)) > 0) {
fcntl(sock, F_SETFL, flags);
return 0;
}
else if (v == 0) {
fcntl(sock, F_SETFL, flags);
return 1; /* timeout */
}
else _pee();
return 0; /* never reach here */
}
貌似在Windows下,只能用这个方法实现connect的timeout。
$ cat ctcp_server.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define _PEXIT \
do {\
char ebuf[64] = {0};\
sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
perror(ebuf);\
exit(errno);\
}while(0)
int main(void) {
struct sockaddr_in myaddr;
socklen_t socklen = sizeof(myaddr);
memset(&myaddr, 0, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
myaddr.sin_port = htons(12345);
/* create a socket */
int sock;
if ((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0)
_PEXIT;
/* bind */
if (bind(sock,(struct sockaddr*)&myaddr,socklen) < 0)
_PEXIT;
/* listen */
if (listen(sock, 5) < 0)
_PEXIT;
/* accept */
int s1;
struct sockaddr_in paddr;
s1 = accept(sock, (struct sockaddr*)&paddr, &socklen);
if (s1 < 0)
_PEXIT;
printf("[accept from %s:%d]\n",
inet_ntoa(*(struct in_addr*)&paddr.sin_addr.s_addr),
ntohs(paddr.sin_port));
/* send welcome */
sleep(1);
char msg[] = "welcome socket in c!";
printf("server send: %s\n", msg);
send(s1, msg, strlen(msg), 0);
/* recv */
char rbuf[64] = {0};
recv(s1, rbuf, 64, 0);
printf("server recv: %s\n", rbuf);
sleep(1);
close(s1);
close(sock);
return 0;
}
int optval = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// Set the SO_REUSEADDR option on the socket
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
setsockopt接口可能会返回错误,以上代码省略了。
int optval = 1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// Set the SO_REUSEPORT option on the socket
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)) == -1) {
perror("setsockopt");
close(sockfd);
exit(EXIT_FAILURE);
}
本文链接:https://cs.pynote.net/net/tcp/202204151/
-- EOF --
-- MORE --