连接

TCP/IP协议规定网络数据传输应采用大端字节序

  • socket地址

struct sockaddr{

unsigned short sa_family;

char sa_data[14];

};

一般不采用上述socket地址,系统兼容性考虑采用sockaddr_in。

#include <netinet/in.h>

typedef uint32_t in_addr_t;

struct in_addr {

in_addr_t s_addr;

};

struct sockaddr_in {

sa_family_t sin_family; //short

in_port_t sin_port; // unsigned short

struct in_addr sin_addr;

};

  • socket

sys/socket.h

int socket(int domain, int type, int protocal);

family: AF_INET, AT_INET6, AF_UNIX

AF_UNIX只能用于单一的(unix)进程间通信

AF_INET是针对Internet的,允许在远程主机之间通信

type: SOCK_STREAM(tcp), SOCK_DGRAM(udp)

protocal:协议 0

return:失败-1,成功fd。

  • bind

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(SERV_PORT);

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

INADDR_ANY标识可以和任何的主机通信。

return: 失败-1(设置errno),成功0

  • listen

int listen(int sockfd, int backlog);

backlog:最多允许有多上个客户端处于连接等待状态,超过忽略。

return:失败-1,成功0

listen完成后,内核自动完成三次握手,与accept无关。

当有客户端发起链接时,服务器端调用accept()返回并接受这个连接。如果有大量客户端发起连接,服务器来不及处理,尚未accept的客户端就处于连接等待状态。

  • accept

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

cliaddr:客户端地址(传出);

addrlen:传入传出(缓冲区大小,地址大小)

return:失败-1,成功:新的文件描述符。

服务器端调用accept()还没有客户端连接请求时,阻塞等待。

链接关闭时,返回错误码ECONNABORTED,此时应重试连接。

perror("accept()");
if((errno == ECONNABORTED) || (errno == EINTR))
continue;
  • connect

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

servaddr:服务器地址。

return:失败-1;成功0。

客户端port由内核自动分配。

当连接不成功时,应阻塞等待。

出错原因:

  1. 指定IP无效。
  2. 无服务器进程。
  3. 超时Timeout。

慢系统调用accept,read,write被信号中断时应该重试。对于accept,如果errno为ECONNABORTED,也应该重试。

读写数据

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

return:成功,返回读取到的字节数;失败-1。

有数据到达,返回读到的字节数;无数据到达,

  • 连接关闭,返回0
  • 连接未关闭,阻塞。

sszie_t write(int fd, const void *buf, size_t count);

return:成功,返回写入的字节数;失败-1。

连接关闭,收到SIGPIPE,返回-1。连接未关闭,

  • 发生流量控制,阻塞;
  • 无流量控制,返回写入字节数。

关闭socket

关闭socket有两个函数close和shutdown。

int shutdown(int sockfd, int howto);

TCP连接是双向的(可读可写),当我们使用close时,会把读写通道都关闭,有时候希望只关闭一个方向,这个时候用shutdown。

howto=0关闭读通道,可以写。

howto=1关别写通道,可以读。

howto=2关闭读写通道,和close一样。

注:shutdown不能代替close,其仅关闭连接,不销毁连接套接字,其后还要close。

多进程程序里,如果有几个进程共享一个socket,如果使用shutdown,那么所有子进程都不能操作了,这个时候只能使用close来关闭子进程的套接子描述符。

总结

总的来说,网络程序是由两个部分组成的--客户端和服务器端。它们建立的步骤一般是:

服务器端

socket-> bind -> listen -> accept

客户端

socket -> connect

注:由于客户端不需要固定端口,因此不必调用bind()。不调用bind(),端口号由内核自动分配。

客户端不是不可以调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),由内核分配端口号,每次重启服务器时端口号都不一样,客户端连接服务器就会遇到麻烦。

示例

客户端程序读数据,服务器端多进程并发写数据。

每次accept一个新客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程。

// ser.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h> #define SERV_PORT 10000
#define MAX_CONN 2
#define STR_SND "I love you!" int main(int argc, char *argv[])
{
int fd, sfd;
int ret = ;
struct sockaddr_in saddr, cliaddr;
socklen_t slen; fd = socket(AF_INET, SOCK_STREAM, );
if(- == fd){
fprintf(stderr, "socket error\n");
exit(EXIT_FAILURE);
} memset(&saddr, , sizeof(struct sockaddr_in));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(SERV_PORT);
ret = bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
if(- == ret){
fprintf(stderr, "bind error\n");
exit(EXIT_FAILURE);
} ret = listen(fd, MAX_CONN);
if( - == ret){
fprintf(stderr, "listen error\n");
exit(EXIT_FAILURE);
} while(){
slen = sizeof(struct sockaddr);
sfd = accept(fd, (struct sockaddr*)&cliaddr, &slen);
if(sfd < ){
// fprintf(stderr, "accept error\n");
       perror("accept()");
            if((errno == ECONNABORTED) || (errno == EINTR))
          continue;
}
signal(SIGCHLD, SIG_IGN);
     signal(SIGPIPE, SIG_IGN);
ret = fork();
if(ret > ){
printf("connet:%s %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
while(){
ret = write(sfd, STR_SND, sizeof(STR_SND));
//ret = write(sfd, STR_SND, 100);
if(ret == -){
if(errno == EPIPE){
close(sfd);
printf("client [%s:%d] disconnect\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
exit(EXIT_SUCCESS);
}
}
sleep();
}
} else if(ret < ) {
perror("fork");
fprintf(stderr, "fork error\n");
}
} close(fd);
return ;
}
// cli.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h> #define SERV_IP "127.0.0.1"
#define SERV_PORT 10000
#define BSIZE 1024 int main(int argc, char *argv[])
{
int fd;
int ret = ;
struct sockaddr_in saddr;
char buf[BSIZE]; fd = socket(AF_INET, SOCK_STREAM, );
if(- == fd){
fprintf(stderr, "socket error\n");
exit(EXIT_FAILURE);
}
memset(&saddr, , sizeof(struct sockaddr_in));
saddr.sin_family = AF_INET;
inet_pton(AF_INET, SERV_IP, &saddr.sin_addr.s_addr);
saddr.sin_port = htons(SERV_PORT);
ret = connect(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
if( - == ret){
fprintf(stderr, "connect error\n");
exit(EXIT_FAILURE);
} while(){
memset(buf, , sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if( ret > ){
if(buf[ret-] != '\n'){
buf[ret-] = '\n';
}
fprintf(stdout, "%d:%s", ret, buf);
fflush(stdout);
} else if(ret == ){ // network disconneted
fprintf(stderr, "disconnect \n");
break;
} else {
if(errno == EINTR){
continue;
}
} } close(fd);
return ;
}

运行结果:

~$gcc ser.c -Wall -o ser
~$gcc cli.c -Wall -o cli
~$./ser
connet:127.0.0.1
connet:127.0.0.1
connet:127.0.0.1
connet:127.0.0.1
connet:127.0.0.1
connet:127.0.0.1
client [127.0.0.1:] disconnect
client [127.0.0.1:] disconnect
client [127.0.0.1:] disconnect
client [127.0.0.1:] disconnect
client [127.0.0.1:] disconnect
client [127.0.0.1:] disconnect
~$./ser
bind error
~$ps aux | grep -w 'ser'
yuxi 0.0 0.0 pts/ S : : ./ser
yuxi 0.0 0.0 pts/ S+ : : grep --color=auto -w ser
~$killall ser
~$ps aux | grep -w 'ser'
yuxi 0.0 0.0 pts/ S+ : : grep --color=auto -w ser

"bind error"是因为父进程一直在监听,没有退出。

从上述示例也可以看出,判断网络断开的方法:read返回sockfd,返回0;write返回-1,errno为EPIPE。

注:SIGPIPE默认动作为程序退出,一旦收到SIGPIPE信号程序就退出,从而无法判断errno,故设置signal(SIGPIPE, SIG_IGN);

可参考:

1. linux网络编程之socket(四):使用fork并发处理多个client的请求和对等通信p2p

socket编程函数的更多相关文章

  1. python socket编程函数介绍

    网上看到一个socket中常用函数的介绍,记录一下 https://blog.csdn.net/rebelqsp/article/details/22109925

  2. socket编程头文件分析

    在socket网络编程中经常用到一些宏定义.结构和函数,这些经常包含在相关的头文件中,使用时直接include相关头文件即可.下面简单描述下相关的一些结构及头文件. 1. sockaddr  / bi ...

  3. socket编程相关的结构体和字节序转换、IP、PORT转换函数

    注意:结构体之间不能直接进行强制转换, 必须先转换成指针类型才可以进行结构体间的类型转换, 这里需要明确的定义就是什么才叫强制转换. 强制转换是将内存中一段代码以另一种不同类型的方式进行解读, 因此转 ...

  4. socket编程:客户端与服务器间的连接以及各函数的用法

    在认真的看UNP之前,一直被socket编程说的云里雾里,今天我要让大家从整天上认识socket编程,让我们知道socket编程的整个流程和各个函数的用法.这样:我们在写一些简单的socket编程时就 ...

  5. socket编程中客户端常用函数

    1 常用函数 1.1   connect() int connect(int sockfd, const struct sockaddr *servaddr, socklen_taddrlen); 客 ...

  6. Socket编程(C语言实现):socket()函数英文翻译

    最近开始研究使用Socket API来网络编程,想着把自己的感想.感悟写下来.我发现在编程之外还有不少概念性的东西要学习.我觉得应该有以下几点吧: 1.得了解下计算机网络的基本概念,如OSI的7层模型 ...

  7. php的socket编程(socket关键几个函数)

    php的socket编程(socket关键几个函数) 一.总结 一句话总结: socket_create.socket_connect.socket_bind.socket_listen.socket ...

  8. socket编程:recvmsg 和 sendmsg 函数

    背景 复习 socket 编程的时候发现了以前没有留意到的 2个函数:recvmsg 和 sendmsg ref : Linux编程之recvmsg和sendmsg函数 知识 先来看看函数原型: #i ...

  9. socket编程里的read和recv函数【转载】

    本文转载自:http://blog.163.com/like12@126/blog/static/63023403201291983117551/ 1.read 与 recv 区别 read 原则: ...

随机推荐

  1. HDUOJ---2642Stars(二维树状数组)

    Stars Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/65536 K (Java/Others)Total Submi ...

  2. HDUOJ---1862EXCEL排序

    EXCEL排序 Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  3. 理解Lucene中的Query

    Query是一个接口,它有很多实现类. QueryParser是Query解析器,用于将一个字符串解析为一个Query对象,这个Query对象可能属于TermQuery,也可能属于PhraseQuer ...

  4. javascript高级程序设计第一章

    看后总结: 1.javascript的组成成分:ECMAscript+DOM+BOM

  5. 'Provide value on 'System.Windows.StaticResourceExtension' threw an exception.'

    产生这个错误的原因是,StaticResource必须先定义再引用,但是DynamicResource就没有这个限制,为避免这个错误出现,可将StaticResource的定义放在Window.xam ...

  6. OGG_GoldenGate日常监控(案例)

    2014-03-11 Created By BaoXinjian  

  7. 线程模型、pthread 系列函数 和 简单多线程服务器端程序

    一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属于1:1模型. (一).N:1用户线程模型 “线程实现”建立在“进程控制”机制之上,由用 ...

  8. C#趣味程序---车牌号推断

    甲说前两位同样,乙说后两位同样,丙说四位的车牌号刚好是一个数的平方.这个车牌号是多少? using System; namespace ConsoleApplication1 { class Prog ...

  9. C# 基础知识 (四).C#简单介绍及托管代码

            暑假转瞬即逝,从10天的支教生活到1周的江浙沪旅游,在这个漫长的暑假中我经历了非常多东西,也学到了非常多东西,也认识到了非常多不足之处!闲暇之余我准备又一次进一步巩固C#相关知识,包含 ...

  10. python标准库介绍——2 os.path模块详解

    == os.path 模块 == ``os.path`` 模块包含了各种处理长文件名(路径名)的函数. 先导入 (import) ``os`` 模块, 然后就可以以 ``os.path`` 访问该模块 ...